schematron_engine/schema.rs
1//! Stage-1 Schematron schema model: `schema`/`ns`/`pattern`/`rule`/
2//! `assert`/`report`, as produced by [`crate::parser::parse`].
3//!
4//! `context` and `test` are kept as raw, unparsed XPath strings — parsing
5//! them is a later phase, gated on the sister crate `xpath-eval` (see
6//! `plan/02-schema-parser.md`, "Voraussetzungen").
7
8/// A parsed Schematron schema (Stage-1 subset, plus Stage-2 `<let>`).
9#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct Schema {
11 pub ns: Vec<NamespaceBinding>,
12 pub lets: Vec<LetBinding>,
13 pub default_phase: Option<String>,
14 pub phases: Vec<Phase>,
15 pub patterns: Vec<Pattern>,
16 pub diagnostics: Vec<Diagnostic>,
17 /// `<properties><property id="..." .../>...</properties>` (ISO
18 /// Schematron 2016 extension, issue #5) — generic, queryable metadata
19 /// this crate has no opinion on beyond parsing and exposing it; nothing
20 /// here affects assertion outcomes.
21 pub properties: Vec<Property>,
22 /// `schema/@queryBinding`, raw as declared (issue #6) — `None` if
23 /// absent (this crate's default: XPath 1.0, same as every declared
24 /// binding this crate accepts; see [`crate::parser::parse`], which
25 /// rejects any binding not equivalent to XPath 1.0 with
26 /// `ParseError::UnsupportedQueryBinding` rather than silently
27 /// evaluating `test`/`context`/`select` expressions as XPath 1.0 when
28 /// the schema declared something else, e.g. `"xslt2"`).
29 pub query_binding: Option<String>,
30}
31
32/// A `<diagnostic id="..." role="...">` — referenced by a [`Check`]'s
33/// `diagnostics` IDREFS list. `message` uses the same rich-content model
34/// as a check's own message (see `plan/08-diagnostics.md`).
35#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct Diagnostic {
37 pub id: String,
38 pub role: Option<String>,
39 pub message: Vec<MessagePart>,
40}
41
42/// A `<property id="..." role="..." scheme="...">` — a `<properties>`
43/// child, generic key/value-ish metadata (ISO Schematron 2016 extension,
44/// issue #5). `message` uses the same rich-content model as a check's own
45/// message. Note: unlike [`Diagnostic`], this crate does not (yet) model
46/// `assert|report/@properties` (the parallel IDREFS attribute the ISO
47/// grammar allows on checks, mirroring `@diagnostics`) — out of scope for
48/// issue #5, which only covers the top-level `<properties>` construct
49/// itself; that attribute is silently unread, same as several other
50/// `assert`/`report` attributes (`flag`, `subject`, ...) this crate
51/// doesn't model.
52#[derive(Clone, Debug, Eq, PartialEq)]
53pub struct Property {
54 pub id: String,
55 pub role: Option<String>,
56 pub scheme: Option<String>,
57 pub message: Vec<MessagePart>,
58}
59
60/// A `<phase id="..."><active pattern="..."/>...</phase>` — a named,
61/// selectable subset of patterns. See `plan/05-phase-selection.md` for how
62/// `Schema.default_phase`/`Schema.phases` feed into
63/// [`crate::engine::evaluate_with_phase`].
64#[derive(Clone, Debug, Eq, PartialEq)]
65pub struct Phase {
66 pub id: String,
67 pub lets: Vec<LetBinding>,
68 /// The `Pattern::id`s this phase activates, in document order (from
69 /// this phase's `<active pattern="...">` children).
70 pub active: Vec<String>,
71}
72
73/// A `<let name="...">` variable binding — either `<let name="..."
74/// value="...">` ([`LetValue::Expr`]) or `<let name="...">`, with literal
75/// XML content instead of a `value` attribute ([`LetValue::Literal`],
76/// issue #3). See `plan/04-let-bindings.md` for evaluation semantics
77/// (schema/pattern-level `<let>` evaluate against the document root,
78/// rule-level against the matched context node, per
79/// [`crate::engine::evaluate`]).
80#[derive(Clone, Debug, Eq, PartialEq)]
81pub struct LetBinding {
82 pub name: String,
83 pub value: LetValue,
84}
85
86/// A [`LetBinding`]'s value — see its doc comment.
87#[derive(Clone, Debug, Eq, PartialEq)]
88pub enum LetValue {
89 /// `<let value="...">` — the raw, unparsed XPath expression (see
90 /// [`Rule::context`]/[`Check::test`] for the same raw-string
91 /// treatment).
92 Expr(String),
93 /// `<let>` with literal-XML (`foreign-element+`) content instead of a
94 /// `value` attribute (issue #3) — pre-computed to its string-value
95 /// (concatenation of every descendant text node, document order) at
96 /// parse time, since `xpath-eval`'s `Value<N>` has no representation
97 /// for a detached literal node-set of a caller-supplied, generic node
98 /// type `N`. Bound as `Value::String` at evaluation time — the same
99 /// treatment XSLT 1.0 gives a variable bound to a literal
100 /// result-tree-fragment by default (usable as a string, not as a
101 /// navigable node-set; this crate has no `exsl:node-set()`-equivalent
102 /// escape hatch, consistent with implementing XPath 1.0 only, no XSLT
103 /// extension functions).
104 Literal(String),
105}
106
107/// An `<ns prefix="..." uri="..."/>` binding, used to resolve prefixes in
108/// `context`/`test` XPath expressions (not evaluated in this phase).
109#[derive(Clone, Debug, Eq, PartialEq)]
110pub struct NamespaceBinding {
111 pub prefix: String,
112 pub uri: String,
113}
114
115/// A `<pattern>` — a named or anonymous group of rules.
116#[derive(Clone, Debug, Eq, PartialEq)]
117pub struct Pattern {
118 pub id: Option<String>,
119 pub lets: Vec<LetBinding>,
120 pub rules: Vec<Rule>,
121}
122
123/// A `<rule context="...">`. `context` is the raw, unparsed XPath
124/// expression selecting the nodes this rule applies to.
125#[derive(Clone, Debug, Eq, PartialEq)]
126pub struct Rule {
127 pub context: String,
128 pub lets: Vec<LetBinding>,
129 pub checks: Vec<Check>,
130}
131
132/// An `<assert>` or `<report>` element. `test` is the raw, unparsed XPath
133/// expression. `message` is the element's rich content, rendered to a
134/// final string only when the check fires — see
135/// [`crate::engine::evaluate`] and `plan/06-value-of-interpolation.md`.
136#[derive(Clone, Debug, Eq, PartialEq)]
137pub struct Check {
138 pub kind: CheckKind,
139 pub test: String,
140 pub id: Option<String>,
141 pub role: Option<String>,
142 pub message: Vec<MessagePart>,
143 /// The `diagnostics="id1 id2"` IDREFS list, raw (not cross-checked
144 /// against `Schema.diagnostics` here — see `plan/08-diagnostics.md`,
145 /// same accepted simplification as `Phase.active`).
146 pub diagnostics: Vec<String>,
147}
148
149/// One piece of a [`Check`]'s message content, in document order.
150///
151/// The ISO Schematron content model for `assert`/`report` also allows
152/// `name`/`emph`/`dir`/`span` markup (and arbitrary foreign-namespace
153/// elements). `<value-of select="...">` (Stage 2, see
154/// `plan/06-value-of-interpolation.md`) and `<name path="...">` (issue #4)
155/// are both evaluated — `emph`/`dir`/`span` are purely presentational
156/// (HTML-ish emphasis/direction/span wrappers) with no data-dependent
157/// semantics, so text nested inside those still contributes via `Text`
158/// (the wrapper tag itself carries no meaning here, same as Stage 1's
159/// plain-text handling), but their own tags are not otherwise modeled.
160#[derive(Clone, Debug, Eq, PartialEq)]
161pub enum MessagePart {
162 Text(String),
163 /// A `<value-of select="...">` — `select` is the raw, unparsed XPath
164 /// expression, evaluated against the firing check's context node.
165 ValueOf(String),
166 /// A `<name path="...">` (or self-closing `<name/>`) — resolves to the
167 /// expanded name (`name()` semantics) of the node `path` selects, or of
168 /// the firing check's own context node when `path` is absent (`None`).
169 /// `path` is the raw, unparsed XPath expression, like `ValueOf`'s
170 /// `select`.
171 Name(Option<String>),
172}
173
174/// Whether a [`Check`] is an `<assert>` (fails when `test` evaluates to
175/// false) or a `<report>` (fires when `test` evaluates to true).
176#[derive(Clone, Copy, Debug, Eq, PartialEq)]
177pub enum CheckKind {
178 Assert,
179 Report,
180}