<if value="3" equal="3">result</if>
if
, because then you can't tell
the difference between actual content and the "else", and it's not elegant to have results of the same test at different
levels of the hierarchy -- sooner or later you'd screw it up. And where do you put negations? Where do you put
boolean functions like "and" or "or"? Into the tests? Then how many different test attributes will you allow? It just
doesn't work and it's driven me crazy until now.
So I think I've got it worked out.
Basically, these elements are involved: decide
, if
, then
, else
,
any
, all
, and unless
. The rules work like this: we scan elements of the top-level
decision element. An "if" is a single test, and if it succeeds, then its attributes are written to the decision element
and its content replaces that of the decision element:
<decide> <if value="3" equal="3" result="yes">content here</if> </decide>thus reduces to
<decide result="yes">content here</decide>
, while
<decide> <if value="3" equal="4" result="yes">content here</if> <decide>just reduces to
<decide/>
.
It's obvious, then, what else
should do. It's the catchall that succeeds when all if
s have
failed:
<decide> <if value="3" equal="2" result="2">it was two</if> <if value="3" equal="4" result="4">it was four</if> <else result="neither">it wasn't two or four</else> </decide>reduces to
<decide result="neither">it wasn't two or four</decide>
. See? So far, so good. Note that we can have multiple
cases, since XML makes it easy to put them there. In this, the decide
tag is really more like a select
statement or a LISP cond
statement.
OK. I'll gloss over the unless
element, which simply is a negated if
, and cut to the chase with
combinations: all
requires that all of its tests succeed, and any
will succeed if any of its
tests succeed. The tests are again if
elements, but in this case, we need a catchall place to put results and
content, so that's where the then
element comes into play. An example might be a good idea:
<decide> <if value="3" equal="2" result="2">it was two</if> <any anyfired="yep"> <if value="3" equal="4" result="4"/> <if value="3" equal="3" result="3"/> <then thenresult="then was here">it was either four or three</then> </any> </decide>will reduce to
<decide anyfired="yep" result="3" thenresult="then was here">it was either four or three</decide>
. The
all
element works in an entirely analogous manner.
Another decide
element may replace any then
or else
for
recursive decisions. I don't like that much, but at the moment I don't see a better solution for implementing nested
tests, for the same reasons that the if-then-else doesn't work: you can't effectively mix arbitrary content with elements
you want to do something with.
See Testing for criteria See Collapsing successful branches into the result WFTK_EXPORT XML * wftk_decide (void * session, XML * datasheet, XML * decision) { XML * elem; XML * elem2; XML * mark; XML * result; XML_ATTR * attr; int fire = 0; if (!decision) return 0; result = xml_create ("decide"); attr = xml_attrfirst (decision); while (attr) { xml_set (result, xml_attrname (attr), xml_attrvalue (attr)); attr = xml_attrnext (attr); } elem = xml_firstelem (decision); while (elem) { if (xml_is (elem, "if") || xml_is (elem, "unless")) { if (_wftk_decide_test (session, datasheet, elem)) { _wftk_decide_collapse (result, elem); return (result); } } else if (xml_is (elem, "else")) { _wftk_decide_collapse (result, elem); return (result); } else if (xml_is (elem, "any")) { elem2 = xml_firstelem (elem); while (elem2) { fire = 0; if (xml_is (elem2, "if") || xml_is (elem2, "unless")) { if (_wftk_decide_test (session, datasheet, elem2)) { _wftk_decide_collapse (result, elem); _wftk_decide_collapse (result, elem2); fire = 1; } } else if (xml_is (elem2, "then") && fire) { _wftk_decide_collapse (result, elem); _wftk_decide_collapse (result, elem2); return (result); } elem2 = xml_nextelem (elem2); } if (fire) { _wftk_decide_collapse (result, elem); return (result); } } else if (xml_is (elem, "all")) { elem2 = xml_firstelem (elem); while (elem2) { if (xml_is (elem2, "if") || xml_is (elem2, "unless")) { if (!_wftk_decide_test (session, datasheet, elem2)) { break; } } else if (xml_is (elem2, "then")) { _wftk_decide_collapse (result, elem); _wftk_decide_collapse (result, elem2); return (result); } elem2 = xml_nextelem (elem2); } if (!elem2) { _wftk_decide_collapse (result, elem); return (result); } } else if (xml_is (elem, "decide")) { mark = wftk_decide (session, datasheet, elem); _wftk_decide_collapse (result, mark); xml_set (result, "loc", xml_attrval (mark, "loc")); xml_free (mark); return (result); } elem = xml_nextelem (elem); } return (result); } |
if
and unless
,
so I'm going to split it out into a function. That makes it easier to extend, too.
int _wftk_decide_test (void * session, XML * datasheet, XML * element) { int result = 0; char * value; char * which; char * test; if (*xml_attrval (element, "equal")) { /* Cutting corners. TODO: maybe some more choices? */ which = "equal"; } else return result; value = wftk_value_interpreta (session, datasheet, xml_attrval (element, "value")); test = wftk_value_interpreta (session, datasheet, xml_attrval (element, which)); if (!strcmp (which, "equal")) { result = !strcmp (value, test); } free (value); free (test); if (xml_is (element, "unless")) return !result; return result; } |
int _wftk_decide_collapse (XML * result, XML * element) { XML_ATTR * attr; XML * mark; attr = xml_attrfirst (element); while (attr) { if ((strcmp (xml_attrname (attr), "value")) || (strcmp (xml_attrname (attr), "equal"))) { xml_set (result, xml_attrname (attr), xml_attrvalue (attr)); } attr = xml_attrnext (attr); } xml_set_nodup (result, "loc", xml_getlocbuf (element)); mark = xml_first (element); while (mark) { xml_append (result, xml_copy (mark)); mark = xml_next (mark); } } |
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. |