Skip to main content

Crate schematron_engine

Crate schematron_engine 

Source
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:

  1. First: implement the subset of ISO Schematron that html-conform actually needs for its assertion layer — enough pattern/rule/ assert/report handling to run its rule set, nothing speculative.
  2. Then: full ISO Schematron conformance — phase selection, <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 a priority attribute; checked against the ISO grammar, rule has 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:

ConstructBehavior
<extends rule="..."> (same-schema rule reuse)supported
<extends href="..."> (external-file rule reuse)MissingAttribute (extends requires @rule) — deliberately not wired up to SchemaResolver even though <include> now is: the ISO reference implementation (iso_dsdl_include.xsl) itself labels extends[@href] “experimental and non-standard”, with substitution semantics that differ from <include> (splices the target’s children, not the element itself)
<let value="...">supported (LetValue::Expr)
<let> with literal-XML (foreign-element+) content instead of valuesupported (LetValue::Literal) — bound as the content’s string-value (concatenation of descendant text, like an XSLT 1.0 result-tree-fragment used as a string); not usable as a navigable node-set (no exsl:node-set()-equivalent escape hatch, consistent with implementing XPath 1.0 only)
<name> in messagesevaluated — resolves to the named node’s expanded name (path="..." selects a node other than the firing check’s own context node, same as <value-of select="...">)
<emph>/<dir>/<span> in messagesaccepted as plain, inert text containers — purely presentational, no data-dependent semantics; their own text still contributes to the message, their tag does not
rule/@context match-pattern semanticsreduced to descendant-or-self::node()/context, a location-path selection — covers the common case, not every exotic XSLT-match-pattern-style context
<include href="..."> (schema composition)ParseError::UnexpectedElement with plain parse() (unchanged — still a hard error, not a silent gap); supported end-to-end with parse_with_resolver given a caller-supplied SchemaResolver — splices the resolved root element in place, recursively. Only the common href-with-no-#fragment form resolving to a single Schematron-namespace root is supported; a #fragment-id cross-document lookup (the ISO reference implementation’s own, itself-labeled-experimental extension) is not — ParseError::UnresolvableInclude/CyclicInclude/InvalidInclude cover the resolver-supplied failure modes
schema/@queryBindingread into Schema.query_binding; "xslt"/"xpath" (case-insensitive) — both XPath 1.0 — are accepted, anything else (e.g. "xslt2", XPath 2.0) is ParseError::UnsupportedQueryBinding rather than silently evaluated as XPath 1.0 anyway
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
<properties>/<property> (ISO Schematron 2016 extension)supported — Schema.properties; assert|report/@properties (the parallel per-check IDREFS attribute) is not modeled, silently unread, same as several other assert/report attributes (flag, subject, …)
Duplicate id on pattern/rule/assert/report/phase/diagnostic/propertyParseError::DuplicateId — one shared, document-wide id space (real XML ID semantics), not one per element type
active/@pattern referencing an unknown or abstract pattern idParseError::UnknownActivePattern
assert|report/@diagnostics referencing an unknown diagnostic idParseError::UnknownDiagnosticReference
Foreign-namespace elements (any namespace other than Schematron’s)always allowed, anywhere — ISO’s own “foreign” content model

In addition to this crate’s own unit/integration tests, tests/schxslt_testsuite.rs runs a vendored, checksum-pinned, third-party conformance corpus (tests/corpus/schxslt-testsuite/, MIT-licensed — see its UPSTREAM.md) end to end. A handful of cases are skipped with a documented reason (see that test’s SKIP list) — mostly constructs this table already lists as unsupported, plus two node kinds (comment()/processing-instruction()) the test-only fixture adapter doesn’t model.

§Installation

[dependencies]
schematron-engine = "0.2"
xpath-eval = "0.2" # implement Document/Node over your own document tree

§License

MIT — see LICENSE.

Structs§

Check
An <assert> or <report> element. test is the raw, unparsed XPath expression. message is the element’s rich content, rendered to a final string only when the check fires — see crate::engine::evaluate and plan/06-value-of-interpolation.md.
Diagnostic
A <diagnostic id="..." role="..."> — referenced by a Check’s diagnostics IDREFS list. message uses the same rich-content model as a check’s own message (see plan/08-diagnostics.md).
LetBinding
A <let name="..."> variable binding — either <let name="..." value="..."> (LetValue::Expr) or <let name="...">, with literal XML content instead of a value attribute (LetValue::Literal, issue #3). See plan/04-let-bindings.md for evaluation semantics (schema/pattern-level <let> evaluate against the document root, rule-level against the matched context node, per crate::engine::evaluate).
NamespaceBinding
An <ns prefix="..." uri="..."/> binding, used to resolve prefixes in context/test XPath 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. See plan/05-phase-selection.md for how Schema.default_phase/Schema.phases feed into crate::engine::evaluate_with_phase.
Position
A 1-based row/column position in the source XML.
Property
A <property id="..." role="..." scheme="..."> — a <properties> child, generic key/value-ish metadata (ISO Schematron 2016 extension, issue #5). message uses the same rich-content model as a check’s own message. Note: unlike Diagnostic, this crate does not (yet) model assert|report/@properties (the parallel IDREFS attribute the ISO grammar allows on checks, mirroring @diagnostics) — out of scope for issue #5, which only covers the top-level <properties> construct itself; that attribute is silently unread, same as several other assert/report attributes (flag, subject, …) this crate doesn’t model.
Report
One fired check: a failed assert, or a true-evaluating report. Checks that do not fire are not represented at all (no fired: bool field) — this is the list of assertion results, not an evaluation trace.
ResolveError
A SchemaResolver failed to resolve an href.
Rule
A <rule context="...">. context is the raw, unparsed XPath expression selecting the nodes this rule applies to.
Schema
A parsed Schematron schema (Stage-1 subset, plus Stage-2 <let>).
SchemaSource
Schema text plus the base URI relative hrefs inside it resolve against.

Enums§

CheckKind
Whether a Check is an <assert> (fails when test evaluates to false) or a <report> (fires when test evaluates to true).
LetValue
A LetBinding’s value — see its doc comment.
MessagePart
One piece of a Check’s message content, in document order.
ParseError
An error encountered while parsing a Schematron schema.
SchematronEvalError
An error evaluating a Schema against a document.

Traits§

SchemaResolver
Resolves an <include href="..."> href (relative to a base URI) to the SchemaSource it names. This crate has no file-system or network access of its own — implement this over whatever loading mechanism fits the caller.

Functions§

evaluate
Evaluates schema against document under ISO’s #DEFAULT phase resolution: schema.default_phase if set, otherwise #ALL (every pattern) — see evaluate_with_phase and plan/05-phase-selection.md. Returns one Report per 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 schema against document, restricted to the patterns activated by the phase named phase_id — None means #ALL (every pattern, ignoring Schema.phases entirely). Some(id) with no matching Schema.phases[_].id is SchematronEvalError::UnknownPhase. See plan/05-phase-selection.md.
parse
Parses a Schematron .sch XML document into a Schema.
parse_with_resolver
Parses a Schematron .sch XML document into a Schema, first resolving every <include href="..."> in it (issue #2) via resolver — unlike parse, which treats <include> as an unsupported, unexpected element (ParseError::UnexpectedElement), same as a typo.