Expand description
§schematron-engine
A pure-Rust implementation of ISO Schematron
— parses a Schematron .sch schema (pattern/rule/assert/report
structure) and evaluates it against a document, producing structured
assertion results (which rules fired, passed, or failed, with their
messages).
Generic and standalone — not tied to HTML, XML parsing, or any specific
document type. Callers provide their own parsed document via a trait; XPath
test="..." expressions are evaluated using
xpath-eval, a separate crate, so
that XPath evaluation stays independently reusable outside of Schematron.
§Scope and the name “engine”
Two-stage goal, deliberately in that order:
- First: implement the subset of ISO Schematron that
html-conformactually needs for its assertion layer — enoughpattern/rule/assert/reporthandling to run its rule set, nothing speculative. - Then: full ISO Schematron conformance —
phaseselection,<let>variable bindings,<value-of>message interpolation, abstract patterns/extends, diagnostics. (Rule conflict resolution — “first matching rule per pattern wins” — is not a stage-1 placeholder awaiting apriorityattribute; checked against the ISO grammar,rulehas no such attribute, so that behavior already is stage 2’s final semantics.)
§Status
Both stages are complete: schema parsing, evaluation against a document,
<let> variable bindings, phase selection, <value-of> message
interpolation, abstract patterns/extends, and diagnostics, plus
referential-integrity checking (duplicate/unknown ids) and hardened
structural validation on top — see “Support matrix” below for exactly
what’s covered and what’s explicitly out of scope.
§Usage
use schematron_engine::{evaluate, parse};
use xpath_eval::Document;
let schema = parse(
r#"<schema xmlns="http://purl.oclc.org/dsdl/schematron">
<pattern>
<rule context="item">
<assert test="@id">item must have an id attribute</assert>
</rule>
</pattern>
</schema>"#,
)?;
let reports = evaluate(&schema, document)?;
for report in &reports {
println!("{:?}: {}", report.kind, report.message);
}This crate never parses XML itself — document above is anything
implementing xpath_eval::Document/Node over your own tree (an XML DOM,
an HTML parser’s tree, …). Note that this means callers depend on
xpath-eval directly too, not just on schematron-engine: Document/
Node are part of evaluate’s own signature (evaluate<D: Document>),
so implementing them requires importing that crate’s traits. See
tests/integration.rs for a complete, runnable
adapter over a real XML tree (via roxmltree), evaluating a realistic
schema end to end.
xpath-eval’s version is a compatibility boundary, not an implementation
detail — Cargo.toml pins a version requirement, and CI always builds and
tests against whatever version that requirement (and Cargo.lock)
resolves to.
§Support matrix
Both ISO Schematron stages this crate set out to implement (see “Scope and the name…” above) are complete. What’s explicitly not supported, and what happens when a schema uses it anyway — surfaced as an error rather than silently doing the wrong thing:
| Construct | Behavior |
|---|---|
<extends rule="..."> (same-schema rule reuse) | supported |
<extends href="..."> (external-file rule reuse) | MissingAttribute (extends requires @rule; resolving @href would need a file/network I/O policy this crate deliberately doesn’t have) |
<let value="..."> | supported; <let> with literal-XML (foreign-element+) content instead of value is a MissingAttribute error |
<name>/<emph>/<dir>/<span> in messages | accepted as plain, inert text containers — their own text still contributes to the message, but their specific semantics (e.g. <name/> resolving to the context node’s name) are not evaluated. Only <value-of> is |
rule/@context match-pattern semantics | reduced to descendant-or-self::node()/context, a location-path selection — covers the common case, not every exotic XSLT-match-pattern-style context |
<include> (schema composition) | ParseError::UnexpectedElement — silently ignoring it would produce an incomplete, wrongly-passing schema, so this is a hard error rather than a silent gap |
| Unknown or misplaced Schematron-namespace elements (typos, elements in the wrong container) | ParseError::UnexpectedElement, not silently dropped — <title>/<p> (prose/documentation) are accepted wherever ISO allows them, even though their content isn’t read |
Duplicate pattern/@id or diagnostic/@id | ParseError::DuplicateId |
active/@pattern referencing an unknown or abstract pattern id | ParseError::UnknownActivePattern |
assert|report/@diagnostics referencing an unknown diagnostic id | ParseError::UnknownDiagnosticReference |
| Foreign-namespace elements (any namespace other than Schematron’s) | always allowed, anywhere — ISO’s own “foreign” content model |
§Installation
[dependencies]
schematron-engine = "0.1"
xpath-eval = "0.2" # implement Document/Node over your own document tree§License
MIT — see LICENSE.
Structs§
- Check
- An
<assert>or<report>element.testis the raw, unparsed XPath expression.messageis the element’s rich content, rendered to a final string only when the check fires — seecrate::engine::evaluateandplan/06-value-of-interpolation.md. - Diagnostic
- A
<diagnostic id="..." role="...">— referenced by aCheck’sdiagnosticsIDREFS list.messageuses the same rich-content model as a check’s own message (seeplan/08-diagnostics.md). - LetBinding
- A
<let name="..." value="...">variable binding.valueis the raw, unparsed XPath expression (seeRule::context/Check::testfor the same treatment). Seeplan/04-let-bindings.mdfor scope (only thevalue-attribute form is supported, not theforeign-element+literal- XML-content form) and evaluation semantics (schema/pattern-level<let>evaluate against the document root, rule-level against the matched context node, percrate::engine::evaluate). - Namespace
Binding - An
<ns prefix="..." uri="..."/>binding, used to resolve prefixes incontext/testXPath expressions (not evaluated in this phase). - Pattern
- A
<pattern>— a named or anonymous group of rules. - Phase
- A
<phase id="..."><active pattern="..."/>...</phase>— a named, selectable subset of patterns. Seeplan/05-phase-selection.mdfor howSchema.default_phase/Schema.phasesfeed intocrate::engine::evaluate_with_phase. - Position
- A 1-based row/column position in the source XML.
- Report
- One fired check: a failed
assert, or a true-evaluatingreport. Checks that do not fire are not represented at all (nofired: boolfield) — this is the list of assertion results, not an evaluation trace. - Rule
- A
<rule context="...">.contextis the raw, unparsed XPath expression selecting the nodes this rule applies to. - Schema
- A parsed Schematron schema (Stage-1 subset, plus Stage-2
<let>).
Enums§
- Check
Kind - Whether a
Checkis an<assert>(fails whentestevaluates to false) or a<report>(fires whentestevaluates to true). - Message
Part - One piece of a
Check’s message content, in document order. - Parse
Error - An error encountered while parsing a Schematron schema.
- Schematron
Eval Error - An error evaluating a
Schemaagainst a document.
Functions§
- evaluate
- Evaluates
schemaagainstdocumentunder ISO’s#DEFAULTphase resolution:schema.default_phaseif set, otherwise#ALL(every pattern) — seeevaluate_with_phaseandplan/05-phase-selection.md. Returns oneReportper fired check, in the order patterns/rules/ checks appear in the schema and nodes are visited (document order within a rule’s matches). - evaluate_
with_ phase - Evaluates
schemaagainstdocument, restricted to the patterns activated by the phase namedphase_id—Nonemeans#ALL(every pattern, ignoringSchema.phasesentirely).Some(id)with no matchingSchema.phases[_].idisSchematronEvalError::UnknownPhase. Seeplan/05-phase-selection.md. - parse
- Parses a Schematron
.schXML document into aSchema.