Interpreting the process definition

Previous: Loading the datasheet for an active process ] [ Top: wftk core index ] [ Next: Global variables: state ]

Interpretation of the process definition, as explained above, is driven by the commands passed in at invocation time. The commands which interest us primarily are 'start' and 'complete'; each of these hands off to the procdef interpreter.

The procdef interpreter runs largely recursively, reflecting the nature of the XML data structure. It is called with an XML structure and runs in the context of the currently loaded datasheet.

Note the use of goto to simulate tail recursion.
XML * queue_procdef (XML * action)
   XML * item;

   if (action == NULL) return;

   if (state == NULL) {
      state = xml_create("state");
      xml_append (datasheet, state);
   if (queue == NULL) {
      queue = xml_create("queue");
      xml_append (state, queue);
   item = xml_create("item");
   xml_setnum (item, "id", idcount++);
   xml_set (item, "type", action->name);
   xml_getloc (action, sbuf, 1023);
   xml_set (item, "loc", sbuf);
   xml_append (queue, item);
   return (item);

void process_procdef()
   XML * item;
   XML * def;
   XML * holder;
   XML * task;
   XML * data;
   XML * next;
   const char * type;
   int count;
   int keep;

   item = xml_first (queue);

   while (item != NULL) {
      if (!strcmp("yes", xml_attrval(item, "block"))) {
         item = xml_next(item);
      def = xml_loc (procdef, xml_attrval(item, "loc"));
      type = xml_attrval (item, "type");

      keep = 0;
      if (!strcmp(type, "workflow") || !strcmp(type, "sequence")) {
         See Handling sequence
      } else if (!strcmp (type, "parallel")) {
         See Handling parallel
      } else if (!strcmp (type, "task")) {
         See Handling task
      } else if (!strcmp (type, "data")) {
         See Handling data
      } else if (!strcmp (type, "situation")) {
         See Handling situation
      } else if (!strcmp (type, "if") || !strcmp (type, "elseif")) {
         See Handling if and elseif
      } else if (!strcmp (type, "alert")) {
         See Handling alerts
      } else if (!strcmp (type, "start")) {
         See start: Starting subprocesses

      if (keep) {
         xml_set (item, "block", "yes");
         item = xml_next(item);
      } else {
         if (strcmp ("workflow", type)) {
            sprintf (sbuf, "queue.item[%d]", xml_attrvalnum (item, "parent"));
            next = xml_loc (queue, sbuf);
            xml_delete (item);
            item = next;
            xml_set (item, "block", "no");
         } else {
            xml_delete (item);
            item = NULL;
   sprintf (sbuf, "%d", idcount);
   xml_set (state, "idcount", sbuf);

Handling sequence
The sequence handler takes care of the sequence tag and also the contents of the outer workflow tag (which are executed sequentially). If the "cur" attribute is not yet yet, then this is the first time we've encountered this sequence, and we queue up the first child of the sequence (and note its location with "cur", of course). Otherwise, we find the child located by "cur", find its next sibling, and queue that up.

If something gets queued, then we block.
if (!strcmp ("", xml_attrval (item, "cur"))) {
   next = xml_firstelem (def);
} else {
   next = xml_loc (procdef, xml_attrval (item, "cur"));
   next = xml_nextelem (next);

if (next) {
   xml_set (queue_procdef (next), "parent", xml_attrval (item, "id"));
   xml_getloc (next, sbuf, sizeof(sbuf) - 1);
   xml_set (item, "cur", sbuf);
   keep = 1;
} else if (!strcmp (type, "workflow")) {
   output ('F', "Process %s complete.", process);

Handling parallel
The parallel item queues up all its children, then blocks. When a child completes, it counts the number of children complete; when its counter decrements to zero, it completes.
if (!strcmp ("", xml_attrval (item, "remaining"))) {
   count = 0;
   next = xml_firstelem (def);
   while (next != NULL) {
      count ++;
      xml_set (queue_procdef (next), "parent", xml_attrval (item, "id"));
      next = xml_nextelem (next);
} else {
   count = xml_attrvalnum (item, "remaining");
xml_setnum (item, "remaining", count);
if (count > 0) keep = 1;

Handling task
Doing a task is nothing more than setting up task data and telling the task manager that the task has been activated. We'll take as the task ID the process ID plus our internal task ID; this will make things easier to handle in the task manager, as it means our task IDs will always be unique (well, assuming the task manager always gives us unique process IDs.)
if (strcmp (xml_attrval (item, "block"), "resume")) {
   sprintf (sbuf, "%s:%s", process, xml_attrval (item, "id"));
   task = xml_create ("task");
   xml_set (task, "id", sbuf);
   xml_append (datasheet, task);
   output ('A', "%s-%s-%s", sbuf, xml_attrval (def, "role"), xml_attrval (def, "label"));

   holder = xml_firstelem (def);
   while (holder != NULL) {
      if (!strcmp (holder->name, "data")) {
         data = xml_create ("data");
         xml_set (data, "id", xml_attrval (holder, "name"));
         xml_set (data, "type", xml_attrval (holder, "type"));
         xml_append (task, data);
      holder = xml_nextelem (holder);
   keep = 1;

Handling data

Handling situation

Handling if and elseif

Handling alerts
For the time being, anyway, we're passing alerts back to the task manager for handling. So our output will be an 'L' line containing the recipient, followed by the contents of the alert, terminated by a line containing "EOF" by itself. The task manager can do whatever it wants with this information.
output ('L', "%s:%s", xml_attrval(def, "type"), xml_attrval(def, "to"));
xml_writecontent (stdout, def);
printf ("\nEOF\n");

start: Starting subprocesses
Previous: Loading the datasheet for an active process ] [ Top: wftk core index ] [ Next: Global variables: state ]

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.