The well-formedness/XPath post covers
two questions the generic XML parser answers on its own: is a document
well-formed, and what does an XPath expression select from it. Two more
engines sit on the same parser: XSLT 1.0
(XSLT.Transform.fst)
rewrites an XML document into another XML document by template, and
Schematron (Schematron.Validate.fst)
checks a document against rules written as XPath assertions rather than a
grammar. Both run live below.
A stylesheet matches a template against the source tree and rebuilds the
output from xsl:value-of and xsl:for-each. This one turns a <library>
of <book> elements into a flat <catalog> of one-line <entry>
elements:
xsltResult = {
const stylesheet = `<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/library">
<catalog><xsl:for-each select="book"><entry><xsl:value-of select="title"/> by <xsl:value-of select="author"/></entry></xsl:for-each></catalog>
</xsl:template>
</xsl:stylesheet>`;
const source = `<library>
<book id="b1"><title>SPARQL 1.1</title><author>W3C</author></book>
<book id="b2"><title>RDF Primer</title><author>W3C</author></book>
</library>`;
return await fn.xsltTransform(stylesheet, source);
}
<catalog><entry>SPARQL 1.1 by W3C</entry><entry>RDF Primer by W3C</entry></catalog> —
two <book> elements walked by xsl:for-each, each field pulled out by
xsl:value-of. Nothing here is a special case of the parser: the
stylesheet is itself parsed by the same Parser.XML.fst module post 25
uses to decide well-formedness, then interpreted as a template against the
source tree.
A DTD or XSD says what shape a document must have. Schematron says what
must be true of it, in plain XPath — "a person element must have an
age child" is one <assert> inside one <rule>:
schematronRules = `<schema xmlns="http://purl.oclc.org/dsdl/schematron">
<pattern>
<rule context="person">
<assert test="age">person must have an age</assert>
</rule>
</pattern>
</schema>`
Run it against a document that fails the rule:
schematronBad = {
const doc = `<person><name>Bob</name></person>`;
const raw = await fn.schematronValidate(schematronRules, doc);
return pretty(raw.findings);
}
One finding, rendered as a table: an assert-fail at context person, test age, message
"person must have an age". Add the missing element and the same rule
clears:
schematronGood = {
const doc = `<person><name>Bob</name><age>30</age></person>`;
const raw = await fn.schematronValidate(schematronRules, doc);
return raw.findings;
}
[] — the same <rule context="person"> walks every person element in
the document via the context XPath, evaluates age as a boolean test at
each one, and this time there's nothing to report.
xsltTransform and schematronValidate are typed fn methods now,
the same shape xmlWellformed/xpathEval already had — a cell calls
fn.xsltTransform(stylesheetXml, sourceXml) / fn.schematronValidate (schematronXml, instanceXml) and gets the unwrapped result directly;
the factoidalNpmEntry ABI lookup and JSON-in/JSON-out envelope live
inside the adapter, not the cell. Every live cell above is pinned in
tests/hub/post27_test.mjs.