Reports
A report is data, not formatted text. One run renders three ways, and can be queried directly instead of scraped back out of prose.
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 personschematron -s rules.sch -f text data.xml # the default
schematron -s rules.sch -f svrl data.xml
schematron -s rules.sch -f json data.xmlEvery 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 reportsFor 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) > 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) < 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) >= $expected - 0.01 and number(total) <= $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 matchedcount_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.