XML Tutorial · Validation · Lesson 11 of 28
RELAX NG and Schematron
RELAX NG as a simpler grammar-based schema language, and Schematron for business rules that grammars can't express.
RELAX NG
RELAX NG (ISO/IEC 19757-2) describes the same structures as XSD with a much smaller, more readable language. It has an XML syntax and a compact syntax:
element order {
attribute id { xsd:integer },
element customer { text },
element item { attribute qty { xsd:positiveInteger }, text }+
}
It's used by DocBook, the TEI, OpenDocument (ODF) and EPUB. libxml2 (lxml, PHP's relaxNGValidateSource) and
Jing validate it.
from lxml import etree
rng = etree.RelaxNG(etree.fromstring(b"""
<element name="order" xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<attribute name="id"><data type="integer"/></attribute>
<element name="customer"><text/></element>
<oneOrMore>
<element name="item">
<attribute name="qty"><data type="positiveInteger"/></attribute>
<text/>
</element>
</oneOrMore>
</element>"""))
for xml in (b'<order id="1"><customer>Ada</customer><item qty="2">Tea</item></order>',
b'<order id="1"><customer>Ada</customer></order>'):
doc = etree.fromstring(xml)
print("valid" if rng.validate(doc) else f"invalid: {rng.error_log.last_error.message}")
Output
valid invalid: Expecting an element , got nothing
Schematron: rules, not grammar
Grammars say what structure is allowed. Schematron (ISO/IEC 19757-3) states rules as XPath assertions, with human-readable messages. It's ideal for business rules such as "the total must equal the sum of the lines" or "a delivery date can't be before the order date":
from lxml import etree, isoschematron
rules = isoschematron.Schematron(etree.fromstring(b"""
<schema xmlns="http://purl.oclc.org/dsdl/schematron">
<pattern>
<rule context="invoice">
<assert test="number(total) = sum(line/@amount)">The total must equal the sum of the lines.</assert>
<assert test="due >= issued">The due date can't be before the issue date.</assert>
</rule>
</pattern>
</schema>"""), store_report=True)
invoice = etree.fromstring(b"""<invoice>
<issued>20261101</issued><due>20261020</due>
<line amount="30"/><line amount="12.5"/>
<total>40</total>
</invoice>""")
print("valid:", rules.validate(invoice))
for failed in rules.validation_report.iter("{http://purl.oclc.org/dsdl/svrl}failed-assert"):
print("-", failed.findtext("{http://purl.oclc.org/dsdl/svrl}text").strip())
Output
valid: False - The total must equal the sum of the lines. - The due date can't be before the issue date.
A common setup is XSD (or RELAX NG) for structure plus Schematron for business rules. Electronic invoicing in the EU (EN 16931) is validated exactly this way.
Test yourself
Which schema language is best for a rule like 'total equals the sum of line amounts'?
SchematronDTDXSD 1.0 without extensionsRELAX NG compact syntax
Show answer
Answer: Schematron
Schematron asserts arbitrary XPath conditions; grammar-based languages describe structure and types.