The Most Common Programming Error with Schematron

Posted on July 18, 2018 by Rick Jelliffe

Schematron is a small, simple language, by design.  The complexity is not in the elements, but sloughed off to the XPaths.

But if there is one mistake that I sometimes see developers make, it is this: people think that all the rules in a pattern will be tried.  In fact, a node will only match one rule per pattern:  the first one lexicially.

Say I have a simple pattern like this:

<pattern>
<rule id="r1" context="a"><assert test=".=1">The a element should have a value of 1, for no reason</assert></rule>
<rule id="r2" context="b"><assert test=".=1">The b element should have a value of 1, for no reason</assert></rule>
<rule id="r3" context="c"><assert test=".=1">The c element should have a value of 1, for no reason</assert></rule>
<rule id="r4" context="c | d"><assert test=".=1">The d element should have a value of 1, for no reason</assert></rule>
<rule id="r5" context="*"><assert test="@id">Elements not a,b,c,d should have an attribute id, for no reason</assert></rule>

</pattern>

If our instance has an element <a> it will fire the rule r1 only, and the assertion for r1 will be tried. No further matching against the other rules will be attempted.

If our instance has an element <c> it will be tested against r1 and r2 which don’t match, so the rule does not fire. It will then be matched with rule r3 successfully, and that rule will fire. However, the rules r4 and r5 will not be tested and not match and not fire. (This rule has the important property of allowing wildcarding and default, for example to let you make a rule to confirm that a pattern is in fact complete by catching unexpected nodes.)

In psuedo-code, you can translate the pattern as an if-then-else chain:

for each node n in document {
if  (n matches rule[r1]) test-assertions(n, rule[r1]);
else if  (n matches rule[r2]) test-assertions(n, rule[r2]);
else if  (n matches rule[r3]) test-assertions(n, rule[r3]);
else if  (n matches rule[r4]) test-assertions(n, rule[r4]);
else if  (n matches rule[r5]) test-assertions(n, rule[r5]);
}

So any node only matches one rule per pattern. But all the assertions for that rule will be tested.  And a node may match in rules multiple patterns.