let svrl = report.to_svrl();   // SVRL, for other Schematron tooling
let json = report.to_json()?;  // JSON, keeping the tree structure
let text = report.to_text();   // for a person
schematron -s rules.sch -f text data.xml   # the default
schematron -s rules.sch -f svrl data.xml
schematron -s rules.sch -f json data.xml

Every output on this page is the real result of running examples/invoice.sch against examples/invoice-bad.xml under the strict phase. The worked example walks through that run →

For people

Text

The first column is the finding's @flag when the schema sets one, and otherwise error for a failed assertion or report for a successful report. Lines beginning - are diagnostics.

examples/invoice-bad.xml:
  error    /invoice[1]
           An invoice must have an id.
  error    /invoice[1]/line[1]
           Quantity must be positive, but is -2.
           - Quantity is the number of units ordered. It must be a positive number.
  error    /invoice[1]/line[2]
           A discount line must have a negative amount, but line has 5.00.
           - Amount is the line total in the invoice currency. Discounts are negative.
  warning  /invoice[1]
           Total is 99.00 but the lines plus tax come to 18.
  4 findings: 4 failed asserts, 0 reports

For other tooling

SVRL

SVRL — Schematron Validation Report Language — is the standard XML vocabulary for a Schematron report, in namespace http://purl.oclc.org/dsdl/svrl, conventionally bound to svrl. Emitting it is what makes this crate's output consumable by existing Schematron tooling.

<?xml version="1.0" encoding="UTF-8"?>
<svrl:schematron-output xmlns:svrl="http://purl.oclc.org/dsdl/svrl" title="Invoice rules" phase="strict">
  <svrl:active-pattern id="structure" name="Structure"/>
  <svrl:fired-rule context="invoice"/>
  <svrl:failed-assert location="/invoice[1]" test="@id" flag="error">
    <svrl:text>An invoice must have an id.</svrl:text>
  </svrl:failed-assert>
  <svrl:active-pattern id="lines" name="Line rules"/>
  <svrl:fired-rule context="line"/>
  <svrl:failed-assert location="/invoice[1]/line[1]" test="number(@qty) &gt; 0" flag="error">
    <svrl:text>
        Quantity must be positive, but is -2.
      </svrl:text>
    <svrl:diagnostic-reference diagnostic="qty-help">
      <svrl:text>
      Quantity is the number of units ordered. It must be a positive number.
    </svrl:text>
    </svrl:diagnostic-reference>
  </svrl:failed-assert>
  <svrl:fired-rule context="line[@type='discount']"/>
  <svrl:failed-assert location="/invoice[1]/line[2]" test="number(@amount) &lt; 0" flag="error">
    <svrl:text>
        A discount line must have a negative amount, but line has 5.00.
      </svrl:text>
    <svrl:diagnostic-reference diagnostic="amount-help">
      <svrl:text>
      Amount is the line total in the invoice currency. Discounts are negative.
    </svrl:text>
    </svrl:diagnostic-reference>
  </svrl:failed-assert>
  <svrl:active-pattern id="totals" name="Totals"/>
  <svrl:fired-rule context="invoice"/>
  <svrl:failed-assert location="/invoice[1]" test="number(total) &gt;= $expected - 0.01 and number(total) &lt;= $expected + 0.01" flag="warning">
    <svrl:text>
        Total is 99.00 but the lines plus tax come to 18.
      </svrl:text>
  </svrl:failed-assert>
</svrl:schematron-output>
Element Emitted when
svrl:active-pattern A pattern begins running against a document. @documents is present only for @documents patterns.
svrl:fired-rule A rule matches a node. Emitted once per matching node.
svrl:failed-assert An assert whose test evaluated false.
svrl:successful-report A report whose test evaluated true.
svrl:text The instantiated human-readable message.
svrl:diagnostic-reference Per @diagnostics reference on the assertion.
svrl:property-reference Per @properties reference on the assertion.
svrl:ns-prefix-in-attribute-values Once per schema ns, so a consumer can interpret @location and @test.

On failed-assert and successful-report: test is the assertion's XPath source text verbatim, location is an absolute XPath to the subject node, and role and flag are resolved — the assertion's if set, otherwise the rule's. see, icon and fpi are passed through when present.

--svrl-findings-only omits the fired-rule events when you want just the findings.

For programs

JSON

The JSON rendering keeps the tree the SVRL rendering flattens: patterns contain rules, and rules contain assertions.

{
  "title": "Invoice rules",
  "phase": "strict",
  "schema_version": null,
  "namespaces": [],
  "patterns": [
    {
      "id": "structure",
      "name": "Structure",
      "documents": null,
      "rules": [
        {
          "id": null,
          "context": "invoice",
          "role": null,
          "flag": null,
          "location": "/invoice[1]",
          "assertions": [
            {
              "kind": "FailedAssert",
              "test": "@id",
              "location": "/invoice[1]",
              "text": "An invoice must have an id.",
              "id": null,
              "role": null,
              "flag": "error",
              "see": null,
              "icon": null,
              "fpi": null,
              "diagnostics": [],
              "properties": []
            }
          ]
        }
      ]
    }
  ]
}

JSON output needs the serde feature, which is on by default.

Instead of parsing text

Query the report directly

report.is_valid();                  // no assert failed
report.count_failures();            // how many did
report.with_flag("error").count();  // findings the schema flagged as errors
report.count_fired_rules();         // zero here means NO context matched

count_fired_rules() is the one to assert on in your own tests.

Zero fired rules is the programmatic form of "my schema does nothing": no rule context matched any node. Check it and a broken schema fails loudly instead of passing silently.

Bidirectional

Reading SVRL back

Report::from_svrl parses an SVRL document into a Report, which makes SVRL support bidirectional rather than write-only.

let report = Report::from_svrl(&svrl)?;
assert_eq!(report.count_failures(), 2);

Two things that buys:

  • Round-trip testing. Every report the crate produces is parsed back and compared against the original, which checks the writer far more thoroughly than asserting on substrings of its output.
  • Comparing processors. A report from another Schematron implementation can be read in and diffed against this one's — the strongest evidence available that the two agree.

The reader rebuilds the tree the writer flattened. A finding appearing before any fired-rule — which is what --svrl-findings-only output looks like — is attached to a synthetic rule, so nothing is lost.

spec/svrl/ The run behind these outputs