Test Schematron rules

A Schematron rule should find errors in XML documents. But how do we know the rule itself is right? An XPath expression that selects too much, or a missing namespace, can reject correct documents or overlook real problems. XSpec can check both.

The starting point is a small set of documents with clear expectations: this one should trigger a message; that one should not.

A title and a cross-reference

Our example has two rules. An article needs a non-empty title. An xref must point to an existing element with the matching xml:id. The simple article model has no application namespace and is not a complete JATS or DocBook validation profile.

examples/06-schematron/article.sch

<?xml version="1.0" encoding="UTF-8"?> 
<sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2">
  <sch:title>Editorial rules for a sample article</sch:title>
  <sch:ns prefix="xml" uri="http://www.w3.org/XML/1998/namespace"/>
  <sch:pattern id="editorial-rules">
    <sch:rule context="article">
      <sch:assert id="article-title" role="error"
        test="normalize-space(title) ne ''">An article must have a non-empty title.</sch:assert>
    </sch:rule>
    <sch:rule context="xref">
      <sch:assert id="xref-target" role="error"
        test="@rid = root(.)//*/@xml:id">A cross-reference must point to an existing ID.</sch:assert>
    </sch:rule>
  </sch:pattern>
</sch:schema>

The rules have the IDs article-title and xref-target. Tests use those IDs to identify a finding without depending on its complete message text. The xml namespace is declared explicitly for the ID expression. A message can then be improved without changing the identity of the rule.

The suite contains a correct article and two separate error cases: an empty title and a reference with no target.

examples/06-schematron/article.xspec

<?xml version="1.0" encoding="UTF-8"?> 
<x:description xmlns:x="http://www.jenitennison.com/xslt/xspec"
  schematron="article.sch">
  <x:scenario label="Accept a complete article">
    <x:context>
      <article><title>Testing XML</title><section xml:id="s1"/>
        <p>See <xref rid="s1"/>.</p></article>
    </x:context>
    <x:expect-valid/>
    <x:expect-not-assert id="article-title"/>
    <x:expect-not-assert id="xref-target"/>
  </x:scenario>
  <x:scenario label="Detect an empty title">
    <x:context><article><title>   </title></article></x:context>
    <x:expect-assert id="article-title"/>
    <x:expect-assert count="1"/>
  </x:scenario>
  <x:scenario label="Detect an unresolved cross-reference">
    <x:context><article><title>Testing XML</title><xref rid="missing"/></article></x:context>
    <x:expect-assert id="xref-target"/>
    <x:expect-not-assert id="article-title"/>
    <x:expect-assert count="1"/>
  </x:scenario>
</x:description>

When does a Schematron test pass?

x:expect-assert expects a failed Schematron assertion to be reported. This takes a moment to get used to: a passing XSpec test can mean that Schematron found an error. The error was deliberately put into the example document.

x:expect-not-assert checks the other direction: this error message must not occur. An assertion ID selects the intended finding, and count checks how many occur. A broken cross-reference should not also produce an unrelated title error.

Testing only invalid documents is not enough. A rule that rejects everything might look successful. Each important rule therefore also needs an example that must be accepted.

x:expect-valid takes message roles into account. Our assertions use role="error". Decide on the expected behavior for warnings and informational messages as well.

Run the rules

With the package runner:

python scripts/run-tests.py --only schematron

Or directly in XSpec's Schematron mode:

"$XSPEC_HOME/bin/xspec.sh" -s -e -j examples/06-schematron/article.xspec

The XSpec 4.0 series uses SchXslt2 for XSLT-based Schematron processing, replacing the previous SchXslt integration. XSpec 4.0.3 includes SchXslt2 1.11.1. When moving from an older environment, check the compiler and its parameters too, including parameter namespaces and phase selection.

A useful starting set for each rule is one correct document, the intended error case and a similar case that must not be rejected. Larger real-world collections can be added later. The small examples usually explain a failure more quickly.

<< back                                                                                                             next >>