Rules and assertions¶
Schematron is assertion-based, not grammar-based. Instead of describing the shape a document must have (the job of XSD), you write a list of claims that should hold and let the engine report any that don't. The vocabulary is tiny: patterns group rules, rules pick the nodes to check, and asserts/reports state the conditions.
The running example¶
Every example on this page checks the same neutral UBL invoice instance:
The building blocks¶
A Schematron schema declares its namespace prefixes once, then groups rules into
patterns. Because UBL elements live in namespaces, every context and test below
is written with the cbc:/cac: prefixes bound in the <ns> declarations:
<ns>binds a prefix to a namespace URI for the whole schema. ThequeryBinding="xslt2"attribute selects XPath 2.0 as the expression language.<pattern>groups related rules. A schema can hold many patterns; they are just organisational containers.<rule context="…">selects the nodes this rule applies to. Thecontextis an XPath that works exactly like an XSLTmatchpattern — here, everycac:InvoiceLineelement in the document.<assert test="…">states a condition that should be true for the context node. Its text is the message reported when the condition is not met.
<assert> fires when the test is FALSE¶
This is the single most important — and most confusing — fact about Schematron.
An assert fires when its test is FALSE
You write the condition that should be true. The assertion FIRES — i.e.
reports a violation — precisely when that condition evaluates to false.
Read <assert test="cbc:LineExtensionAmount">…</assert> as "I assert this
line has an amount; complain if it doesn't." Beginners often write the
test as if it described the error — that inverts the logic and reports
every valid document.
<report> is the inverse¶
<report test="…"> is the mirror image: it fires when its test is true. Use
it to flag something that is present but shouldn't be.
| rules.sch | |
|---|---|
- Fires when the test holds — a negative amount is exactly what we want to
catch. The
<is the escaped<, since the schema is itself XML.
So the two are duals: assert test="X" is equivalent to report test="not(X)",
and vice versa. Pick whichever reads more naturally — assert for "this must
hold", report for "this must not happen".
First matching rule wins¶
Within a single pattern, each node is handled by the first rule whose
context matches it. Once a node is claimed by a rule, later rules in the same
pattern are not also applied to it — exactly like XSLT template matching,
where one node activates one template. (See
XSLT templates for the same idea on the transform side.)
| rules.sch | |
|---|---|
- Line 1 matches this rule, so only its assert is evaluated for that node.
- Line 2 falls through to here. The first line never reaches this rule, even
though its context (
cac:InvoiceLine) would also match it. To check several independent conditions on the same node, put the asserts in one rule — or in separate patterns.
A couple of direct rules¶
Direct (non-abstract) rules test the running invoice straight away. Here two patterns require a line amount on every invoice line and an issue date on the invoice:
- The context can be any XPath, including an absolute path to the document element.
Against the instance above, both lines carry a cbc:LineExtensionAmount and the
invoice carries a cbc:IssueDate, so nothing fires — the document passes:
No assertions fired — document is valid.
Now remove the amount from the second line (delete its
<cbc:LineExtensionAmount>). The first rule's test is false for that node, so
the assert fires and emits its message — with cbc:ID filled in from the
context node:
Invoice line 2 is missing a line amount.
A real EN16931 rule¶
The same shapes appear in the public CEN EN16931 UBL Schematron (EUPL/Apache).
A direct rule on cac:InvoiceLine asserts the presence of the line net amount,
with a flag marking severity and a stable, bracketed business-rule message:
| EN16931-UBL.sch | |
|---|---|
flag="fatal"is an arbitrary label the calling tool can act on (e.g. to distinguish blocking errors from warnings); Schematron does not interpret it itself. The message follows the EN16931 convention of a[BR-…]code followed by the human-readable rule text.
Other rules in the same ruleset read identically — for example the assert behind the specification identifier:
[BR-01]-An Invoice shall have a Specification identifier (BT-24).
Note that even this "real" rule is just the building blocks from this page: a
<rule> with a context, an <assert> whose test states what must be true,
and a message emitted when it isn't.
Next¶
Context and tests — how the context XPath establishes
the node a rule runs against, and how test expressions are evaluated relative
to it.