Skip to main content

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}