This page is fully self-contained: no external network request is possible (see the Content-Security-Policy above). A live mode twin of this same post can load map tiles and remote SPARQL endpoints.

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.

XSLT: reshaping a document#

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.

Schematron: rules instead of a grammar#

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.

Reaching these from the browser#

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.