Dealing with values

Previous: Dealing with requests ] [ Top:  ] [ Next: Getting and setting process status ]

OK, so actions may be the heart of workflow, but without data manipulation there's not much point. The basic purpose of the datasheet is of course to store data, so let's see how we do it.

The basic actions we can take for a value are, of course, getting and setting it. In wftk, there is no requirement that a variable be allocated before it's written. As the namespace for a value is already automatically the process it's in, there is no real need to be terribly disciplined about how it's used. So to write a named value, I simply set it. For convenience in C, I've provided both string and numeric versions of the data access functions, but rest assured that all values are stored as text variables.

In order to be able to handle null values, we provide the wftk_value_isnull and wftk_value_makenull functions.
 
WFTK_EXPORT XML * wftk_value_find (void * session, XML * datasheet, const char * name)
{
   if (!datasheet) return NULL;
   return xml_locf (datasheet, ".data[%s]", name);
} 
WFTK_EXPORT XML * wftk_value_make (void * session, XML * datasheet, const char * name)
{
   XML * data;

   if (!datasheet) return NULL;
   data = xml_locf (datasheet, ".data[%s]", name);
   if (!data) {
      data = xml_create ("data");
      xml_set (data, "id", name);
      xml_append (datasheet, data);
   }

   return data;
} 
WFTK_EXPORT const char * wftk_value_get     (void * session, XML * datasheet, const char * name)
{
   XML * data;
   WFTK_ADAPTOR * ad;

   if (!name) return "";
   if (*name == '!') return _wftk_value_special (session, datasheet, name);

   if (strchr (name, ':')) {
      ad = wftk_get_adaptor (session, DATASTORE, name);
      if (!ad) return "";
      data = wftk_call_adaptor (ad, "get", datasheet, name);
      wftk_free_adaptor (session, ad);
      return (xml_attrval (data, "value"));
   }

   data = wftk_value_find (session, datasheet, name);
   if (!data) return "";

   if (!strcmp (xml_attrval (data, "null"), "yes")) return "";

   return (xml_attrval (data, "value"));
}
WFTK_EXPORT int    wftk_value_get_num (void * session, XML * datasheet, const char * name)
{
   const char * value;

   if (!name) return 0;
   value = wftk_value_get (session, datasheet, name);
   if (!value) return 0;
   return (atoi (value));
}

WFTK_EXPORT int wftk_value_isnull (void * session, XML * datasheet, const char * name)
{
   XML * data;
   WFTK_ADAPTOR * ad;

   if (!name) return 1; /* Null name is, well, null.  Logical, right? */
   if (*name == '!') return 0; /* Special value is never null.  Why?  Because I say so. */

   data = wftk_value_find (session, datasheet, name);
   if (!data) return 1;

   if (!strcmp (xml_attrval (data, "null"), "yes")) return 1;
   return 0;
}

WFTK_EXPORT int wftk_value_set (void * session, XML * datasheet, const char * name, const char * value)
{
   XML * data;
   char * stuff;
   WFTK_ADAPTOR * ad;

   if (!name) return 0;
   if (*name == '!') return 0;  /* Refuse to set special values. */

   if (strchr (name, ':')) {
      ad = wftk_get_adaptor (session, DATASTORE, name);
      if (!ad) return 0;
      wftk_call_adaptor (ad, "set", datasheet, name, value);
      wftk_free_adaptor (session, ad);
      return 1;
   }

   data = wftk_value_make (session, datasheet, name);
   if (!data) return 0;

   if (strcmp (wftk_value_get (session, datasheet, name), value)) {
      wftk_enactment_write (session, datasheet, data, "was", wftk_value_get (session, datasheet, name));
      xml_set (data, "value", value);
   }
   if (!strcmp (xml_attrval (data, "null"), "yes")) xml_set (data, "null", "");

   /* 07/22/01 - implementing data aliasing (local cache of remote data) with write-though. */
   if (*xml_attrval (data, "storage")) {
      ad = wftk_get_adaptor (session, DATASTORE, xml_attrval (data, "storage"));
      if (ad) {
         wftk_call_adaptor (ad, "store", data);
         wftk_free_adaptor (session, ad);
      }
   }
   return 1;
}
WFTK_EXPORT int    wftk_value_set_num (void * session, XML * datasheet, const char * name, int value)
{
   XML * data;
   WFTK_ADAPTOR * ad;
   char valbuf[sizeof(int) * 3 + 1];

   if (!name) return 0;
   if (*name == '!') return 0;

   sprintf (valbuf, "%d", value);
   return (wftk_value_set (session, datasheet, name, valbuf));
} 

WFTK_EXPORT int wftk_value_makenull (void * session, XML * datasheet, const char * name)
{
   XML * data;
   WFTK_ADAPTOR * ad;

   if (!name) return 0;
   if (*name == '!') return 0; /* Refuse to set special values. */

   if (strchr (name, ':')) {
      ad = wftk_get_adaptor (session, DATASTORE, name);
      if (!ad) return 0;
      wftk_call_adaptor (ad, "makenull", datasheet, name);
      wftk_free_adaptor (session, ad);
      return 1;
   }

   data = wftk_value_make (session, datasheet, name);
   if (!data) return 0;
   wftk_enactment_write (session, datasheet, data, "was", wftk_value_get (session, datasheet, name));
   xml_set (data, "null", "yes");
   return 1;
}   
Besides just a plain get, we need a little value interpreter. This is basically a sprintf-like affair, which takes a string and replaces named values in it which are marked like ${this}. Later we'll need boolean expressions and stuff, and that will also probably fall into this value area.

July 18, 2001: Finally got around to building a version of the interpreter which builds its value as it goes (via malloc, or more specifically, via XMLAPI's xml_attrcat). This resolves a lot of the fixed-buffer issues I had with the earlier code.

wftk_value_interpret returns the number of bytes it copied to the buffer.
wftk_value_interpreta returns a new buffer. You're on your own for deallocation.
 
WFTK_EXPORT int    wftk_value_interpret (void * session, XML * datasheet, const char * spec, char * buffer, int bufsize)
{
   int count = 0;
   const char *value;
   char namebuf[256];
   int i;

   bufsize--; /* Leave room for the null. */
   while (*spec) {
      if (spec[0] == '$' && spec[1] == '{') {
         i = 0;
         spec += 2;
         while (*spec && *spec != '}' && i < sizeof(namebuf) - 1) {
            namebuf[i++] = *spec++;
         }
         if (*spec == '}') spec++;
         namebuf[i] = '\0';
         value = wftk_value_get (session, datasheet, namebuf);
         while (*value && bufsize) {
            *buffer++ = *value++;
            count++;
            bufsize--;
         }
      } else {
         *buffer++ = *spec++;
         count++;
         bufsize--;
      }
      if (!bufsize) break;
   }
   *buffer = '\0';
   return count;
}

WFTK_EXPORT char * wftk_value_interpreta (void * session, XML * datasheet, const char * spec)
{
   int count = 0;
   char *value;
   char * mark;
   char namebuf[256];
   int len;
   int i;
   XML * val;

   mark = strstr (spec, "${");
   if (!mark) return strdup (spec); /* Easy case!  No interpretation! */

   val = xml_create ("value");
   xml_set (val, "value", "");

   while (mark) {
      xml_attrncat (val, "value", spec, mark - spec);
      spec = mark + 2;
      mark = strchr (spec, '}');
      if (mark) {
         len = mark - spec;
      } else {
         len = strlen (spec);
      }
      if (len > sizeof (namebuf) - 1) len = sizeof (namebuf) - 1;

      xml_attrcat (val, "value", wftk_value_get (session, datasheet, namebuf));

      if (mark) {
         spec = mark + 1;
         mark = strstr (spec, "${");
      }
   }
   value = strdup (xml_attrval (val, "value"));
   xml_free (val);
   return value;
}
Sometimes special values are nice. For instance, the current time is always nice to have. So I'm rather arbitrarily defining '!' to be the special-value character. So, for instance, '!now' is the current time, in the following format: 2001-03-19 06:07:00 AM. This is in accordance with the ISO 8601 standard for date and time formats. The ISO standard suggests putting a 'T' between the date and the time, but doesn't require it, and I find that to be less conveniently readable to the human eye, so I'm not going to do it. Your mileage may vary.
 
const char * _wftk_value_special (void * session, XML * datasheet, const char * name)
{
   struct tm * timeptr;
   time_t julian;
   static char value[64]; /* Boy, this is dangerous.  TODO: there's gotta be a better way. */

   if (!strcmp (name, "!now")) {
      time (&julian);
      timeptr = localtime (&julian);
      sprintf (value, "%04d-%02d-%02d %02d:%02d:%02d", timeptr->tm_year + 1900, timeptr->tm_mon + 1, timeptr->tm_mday,
                                                       timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec);
      return (value);
   }

   return ("");
}
There's more to wftk than plain string values. In addition to the basic get and set, we can also set the type of a value, and we can determine where it's actually stored (the default is that the value is stored directly in the datasheet, but that might not be convenient.) Types are defined by type adaptors, storage is defined by datastore adaptors. A storage adaptor can work in either of two modes; the value can either be retrieved each time from the adaptor (example: the role adaptor simply presents the role assignments as data), or the value can be cached locally and gotten in the normal manner, and the adaptor need only be invoked when a value is written (example: the currecord adaptor writes new values to the database, but the current value is always available locally with no database interaction.)
 
WFTK_EXPORT int    wftk_value_settype (void * session, XML * datasheet, const char * name, const char * type)
{
   XML * data;
   WFTK_ADAPTOR * ad;

   if (!name) return 0;
   if (*name == '!') return 0;  /* Refuse to set special values. */

   if (strchr (name, ':')) return 0; /* Can't set types for alternate stores -- the answer will be aliases, later. */

   data = wftk_value_make (session, datasheet, name);
   if (!data) return 0;

   xml_set (data, "type", type);
   return 1;
}
WFTK_EXPORT int    wftk_value_define  (void * session, XML * datasheet, const char * name, const char * storage)
{
   XML * data;
   data = wftk_value_make (session, datasheet, name);
   if (data) {
      xml_set (data, "storage", storage);
   }
}
Data manipulation requires more than just setting of constant values. For that reason, there's a calculation function as well, which sets a value by evaluating its argument. There will soon be an "eval:" datasource for doing calculations, but that's going to have to wait until sometime post-v1.0. Basically, wftk_value_calc just runs wftk_value_interpret on its incoming value and sets the named value accordingly.
 
WFTK_EXPORT int    wftk_value_calc    (void * session, XML * datasheet, const char * name, const char * value) { return 0; }
Just for ease of use, we provide HTML formatting as part of the data type adaptor. Here's where that gets done. Note that what actually gets returned is HTML-like XML, not HTML. Use the HTML functions from the XMLAPI to write it out.
 
WFTK_EXPORT XML  * wftk_value_html      (void * session, XML * datasheet, const char * name)
{
   XML * field;
   XML * data;
   WFTK_ADAPTOR * ad;

   if (!name) return 0;
   if (*name == '!') return (xml_createtext (_wftk_value_special (session, datasheet, name)));

   if (strchr (name, ':')) {
      ad = wftk_get_adaptor (session, DATASTORE, name);
      if (!ad) return 0;
      data = wftk_call_adaptor (ad, "get", datasheet, name);
      wftk_free_adaptor (session, ad);
      field = xml_create ("input");
      xml_set (field, "name", name);
      xml_set (field, "value", xml_attrval (data, "value"));
      xml_delete (data);
      return (field);
   }

   data = wftk_value_find (session, datasheet, name);
   if (!data) {
      field = xml_create ("input");
      xml_set (field, "name", name);
      xml_set (field, "value", "");
      return (field);
   }

   if (*xml_attrval (data, "type")) {
      ad = wftk_get_adaptor (session, DATATYPE, xml_attrval (data, "type"));
      if (ad) {
         field = wftk_call_adaptor (ad, "html", datasheet, data);
         wftk_free_adaptor (session, ad);
         return (field);
      }
   }

   field = xml_create ("input");
   xml_set (field, "name", name);
   if (*xml_attrval (data, "size")) {
      xml_set (field, "size", xml_attrval (data, "size"));
   }
   if (!strcmp (xml_attrval (data, "null"), "yes")) {
      xml_set (field, "value", "");
   } else {
      xml_set (field, "value", wftk_value_get (session, datasheet, name));
   }

   return (field);
}

WFTK_EXPORT XML  * wftk_value_htmlblank (void * session, XML * datasheet, const char * name)
{
   XML * field;
   XML * data;
   WFTK_ADAPTOR * ad;

   if (!name) return 0;
   if (*name == '!') return (xml_createtext (_wftk_value_special (session, datasheet, name)));

   if (strchr (name, ':')) {
      field = xml_create ("input");
      xml_set (field, "name", name);
      xml_set (field, "value", "");
      return (field);
   }

   data = wftk_value_find (session, datasheet, name);
   if (!data) {
      field = xml_create ("input");
      xml_set (field, "name", name);
      xml_set (field, "value", "");
      return (field);
   }

   if (*xml_attrval (data, "type")) {
      ad = wftk_get_adaptor (session, DATATYPE, xml_attrval (data, "type"));
      if (ad) {
         field = wftk_call_adaptor (ad, "htmlblank", datasheet, data);
         wftk_free_adaptor (session, ad);
         return (field);
      }
   }

   field = xml_create ("input");
   xml_set (field, "name", name);
   if (*xml_attrval (data, "size")) {
      xml_set (field, "size", xml_attrval (data, "size"));
   }
   xml_set (field, "value", "");

   return (field);
}
And finally, for inspection of a datasheet, we'll want to be able to list values and get specific information about them.
 
WFTK_EXPORT int wftk_value_list    (void * session, XML * datasheet, XML * list) {
   int counter = 0;
   XML * pointer = xml_firstelem (datasheet);
   XML * value;

   if (!list) return 0;

   while (pointer) {
      if (xml_is (pointer, "data")) {
         counter++;
         value = xml_create ("data");
         xml_set (value, "id", xml_attrval (pointer, "id"));
         xml_set (value, "value", wftk_value_get (session, datasheet, xml_attrval (pointer, "id"))); /* OK, not the most efficient method..*/
         xml_set (value, "type", xml_attrval (pointer, "type"));
         xml_append (list, value);
      }
      pointer = xml_nextelem (pointer);
   }

   xml_setnum (list, "count", counter);
   return counter;
}

WFTK_EXPORT XML  * wftk_value_info    (void * session, XML * datasheet, const char * name) { return 0; }
Previous: Dealing with requests ] [ Top:  ] [ Next: Getting and setting process status ]


This code and documentation are released under the terms of the GNU license. They are additionally copyright (c) 2000, Vivtek. All rights reserved except those explicitly granted under the terms of the GNU license.