Skip to main content

sup_xml_core/xsd/
schema.rs

1//! Compiled-schema data structures.
2//!
3//! A [`Schema`] is the output of the schema compiler — a graph of
4//! reference-counted declarations and type definitions, ready for the
5//! validator to consume.
6//!
7//! Names are [`QName`]s carrying the namespace URI alongside the local
8//! name; the schema compiler resolves prefixes to URIs at compile time
9//! so the runtime never has to deal with prefix bookkeeping.
10
11use std::collections::HashMap;
12use std::sync::Arc;
13
14use super::types::{ComplexType, SimpleType};
15
16// ── compile-time options ─────────────────────────────────────────────────────
17
18/// Which XSD version the compiler should target.  XSD 1.1 is a strict
19/// superset of 1.0 — every 1.0 schema is also a valid 1.1 schema — so
20/// the choice only matters when a schema *uses* a 1.1-specific
21/// construct (`xs:assert`, `xs:alternative`, `xs:override`, wildcard
22/// `notQName` / `notNamespace`, the 1.1-added built-in datatypes, …).
23///
24/// Defaults to [`Xsd10`](SchemaVersion::Xsd10) — matches libxml2's
25/// behaviour, matches Xerces and Saxon's defaults, and avoids the
26/// "schema looked like it compiled but the 1.1 constraints aren't
27/// actually being enforced" failure mode that auto-detection produces
28/// when authors forget to set `vc:minVersion`.
29#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
30pub enum SchemaVersion {
31    /// Strict XSD 1.0 — reject any 1.1 construct.  Equivalent to
32    /// libxml2's behaviour.  Recommended for any pipeline that's
33    /// migrating from libxml2 or whose schemas predate 2012.
34    #[default]
35    Xsd10,
36    /// Strict XSD 1.1 — accept 1.1 constructs unconditionally,
37    /// without requiring `vc:minVersion` on the schema document.
38    Xsd11,
39    /// Hybrid: start in 1.0 mode, auto-promote to 1.1 when the schema
40    /// document carries `vc:minVersion="1.1"` on its root element.
41    /// Convenient for mixed corpora; explicit opt-in via
42    /// [`Xsd11`](SchemaVersion::Xsd11) is still preferred for
43    /// production pipelines because most 1.1 schemas in the wild
44    /// don't bother to set `vc:minVersion`.
45    Auto,
46}
47
48/// Knobs for [`Schema::compile_str_with_options`](Schema::compile_str_with_options)
49/// / [`Schema::compile_with_options`](Schema::compile_with_options).
50///
51/// Today the only knob is [`version`](Self::version); more options
52/// (strictness toggles for libxml2-compat, optional warning callbacks,
53/// etc.) will land on this struct so we don't break the API every
54/// time we add a flag.
55#[derive(Default, Clone, Debug)]
56pub struct SchemaOptions {
57    pub version: SchemaVersion,
58}
59
60// ── qualified names ──────────────────────────────────────────────────────────
61
62/// A namespace-qualified XML name.  XSD operates entirely in terms of
63/// these — local names alone are insufficient when a schema imports
64/// other namespaces.
65#[derive(Debug, Clone, PartialEq, Eq, Hash)]
66pub struct QName {
67    /// `None` when the name is in no namespace.
68    pub namespace: Option<Arc<str>>,
69    pub local:     Arc<str>,
70}
71
72impl QName {
73    pub fn new(ns: Option<&str>, local: &str) -> Self {
74        Self {
75            namespace: ns.map(Arc::from),
76            local:     Arc::from(local),
77        }
78    }
79
80    /// XSD-spec built-in namespace (`http://www.w3.org/2001/XMLSchema`).
81    pub const XSD_NS: &'static str = "http://www.w3.org/2001/XMLSchema";
82
83    /// Build a QName in the XSD spec namespace.
84    pub fn xsd(local: &str) -> Self {
85        Self::new(Some(Self::XSD_NS), local)
86    }
87}
88
89impl std::fmt::Display for QName {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        match &self.namespace {
92            Some(ns) => write!(f, "{{{ns}}}{}", self.local),
93            None     => f.write_str(&self.local),
94        }
95    }
96}
97
98// ── element declaration ──────────────────────────────────────────────────────
99
100/// `<xs:element>` declaration — top-level or local.
101#[derive(Debug)]
102pub struct ElementDecl {
103    pub name:        QName,
104    /// Resolved type — may be a simple or complex type.  Anonymous
105    /// inline types end up here too.
106    pub type_def:    TypeRef,
107    /// `xs:nillable` attribute.  `xsi:nil="true"` instances on this
108    /// element are valid only when this is `true`.
109    pub nillable:    bool,
110    /// `default="…"` value applied when the element is empty.
111    pub default:     Option<String>,
112    /// `fixed="…"` value the element *must* match if present.
113    pub fixed:       Option<String>,
114    /// `abstract="true"` — element cannot appear in instances; only
115    /// substitutes for it can.
116    pub abstract_:   bool,
117    /// Head of substitution group this element substitutes into,
118    /// resolved by the schema compiler.
119    pub substitution_group: Option<QName>,
120    /// `block="restriction|extension|substitution"` — disallowed
121    /// derivation modes for substitutes.  Stored as a flag set.
122    pub block:       BlockSet,
123    /// `final="restriction|extension"` — derivation forms that must not
124    /// derive from this element.
125    pub final_:      BlockSet,
126    /// `<xs:key>` / `<xs:keyref>` / `<xs:unique>` constraints declared
127    /// directly on this element.  Empty for the common case.  Each
128    /// constraint scopes to the subtree rooted at this element.
129    pub identity:    Vec<super::identity::IdentityConstraint>,
130}
131
132bitflags::bitflags! {
133    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
134    pub struct BlockSet: u8 {
135        const RESTRICTION = 0b00001;
136        const EXTENSION   = 0b00010;
137        const SUBSTITUTION = 0b00100;
138        /// `final="list"` — block derivation of a list type whose
139        /// `itemType` is this type (XSD §3.16.6).
140        const LIST        = 0b01000;
141        /// `final="union"` — block derivation of a union type whose
142        /// `memberTypes` includes this type (XSD §3.16.6).
143        const UNION       = 0b10000;
144    }
145}
146
147// ── attribute declaration ────────────────────────────────────────────────────
148
149/// `<xs:attribute>` — top-level or local.
150#[derive(Debug)]
151pub struct AttributeDecl {
152    pub name:     QName,
153    /// Always a simple type per spec.
154    pub type_def: Arc<SimpleType>,
155    pub default:  Option<String>,
156    pub fixed:    Option<String>,
157    /// XSD 1.1 § 3.2.2 — when `true`, this attribute's value is
158    /// inherited into descendant elements' assertion / conditional-
159    /// type-assignment contexts.  Default is `false`.  Today's
160    /// validator records the flag for forward compatibility; it has
161    /// no observable effect until `xs:assert` / `xs:alternative`
162    /// land (those consume inherited attributes via the dynamic
163    /// XPath context).  Compiling a schema with `inheritable="true"`
164    /// requires [`SchemaVersion::Xsd11`].
165    pub inheritable: bool,
166}
167
168/// How an attribute appears within a complex type.
169#[derive(Debug, Clone)]
170pub struct AttributeUse {
171    pub use_kind: AttributeUseKind,
172    pub decl:     Arc<AttributeDecl>,
173    /// Local override of the declaration's default, if any.
174    pub default:  Option<String>,
175    /// Local override of the declaration's fixed value.
176    pub fixed:    Option<String>,
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180pub enum AttributeUseKind { Required, Optional, Prohibited }
181
182// ── XSD 1.1 assertions ───────────────────────────────────────────────────────
183
184/// XSD 1.1 `<xs:assert test="…">` / `<xs:assertion test="…">`.
185///
186/// Captures an XPath 2.0 expression that's evaluated during
187/// validation: against the element being validated (for `xs:assert`
188/// on a complex type) or against the simple-type value bound to
189/// `$value` (for `xs:assertion` inside a simple-type restriction).
190/// The expression is stored as its source string and parsed lazily
191/// by the validator.
192#[derive(Debug, Clone)]
193pub struct Assertion {
194    /// The verbatim XPath expression from the `test=` attribute.
195    pub test: String,
196    /// Namespace bindings in scope at the assert/assertion element —
197    /// used to resolve prefixed names in `test` at evaluation time.
198    pub namespaces: Vec<(Option<String>, String)>,
199    /// XSD 1.1 `xpathDefaultNamespace` — the URI an unprefixed
200    /// element name in `test` defaults to.  `None` means the no-
201    /// namespace default.
202    pub xpath_default_namespace: Option<String>,
203}
204
205// ── content model ────────────────────────────────────────────────────────────
206
207/// A type's body shape: nothing, a simple value, or a particle tree.
208#[derive(Debug, Clone)]
209pub enum ContentModel {
210    /// `<xs:complexType><xs:complexContent>` with no children — empty
211    /// element type.
212    Empty,
213    /// Simple-content type — body is a parsed value of a simple type
214    /// (still allows attributes via the surrounding ComplexType).
215    Simple(Arc<SimpleType>),
216    /// Element-only or mixed content described by a particle tree.
217    Complex { root: Particle, mixed: bool },
218}
219
220/// One particle in a content model — an element, a wildcard, or a
221/// nested group — with occurrence bounds.
222#[derive(Debug, Clone)]
223pub struct Particle {
224    pub min_occurs: u32,
225    pub max_occurs: MaxOccurs,
226    pub term:       Term,
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum MaxOccurs { Bounded(u32), Unbounded }
231
232impl MaxOccurs {
233    pub fn allows(self, n: u32) -> bool {
234        match self {
235            MaxOccurs::Bounded(m) => n <= m,
236            MaxOccurs::Unbounded  => true,
237        }
238    }
239}
240
241#[derive(Debug, Clone)]
242pub enum Term {
243    /// Reference to an element declaration (top-level by name, or a
244    /// local declaration inlined into the particle).
245    Element(Arc<ElementDecl>),
246    /// Nested particle group: sequence / choice / all.  Particles live
247    /// in an `Arc<[Particle]>` so the validator's cursor can clone it
248    /// (one refcount bump) instead of allocating a fresh `Vec` per
249    /// element entered.
250    Group {
251        kind:      GroupKind,
252        particles: Arc<[Particle]>,
253    },
254    /// `<xs:any>` wildcard.
255    Wildcard(Wildcard),
256    /// `<xs:group ref="...">` reference to a named model group.
257    /// Emitted by the parser; replaced with the referenced group's
258    /// `Group` particle by a post-pass during schema compile.  The
259    /// validator and DFA builder should never see this variant.
260    GroupRef(QName),
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
264pub enum GroupKind { Sequence, Choice, All }
265
266/// `<xs:any>` / `<xs:anyAttribute>` wildcard configuration.
267#[derive(Debug, Clone)]
268pub struct Wildcard {
269    pub namespaces:       NamespaceConstraint,
270    pub process_contents: ProcessContents,
271    /// XSD 1.1 § 3.10.4 `notQName` — element/attribute names excluded
272    /// from the wildcard's match set.  Empty in 1.0 mode (the parser
273    /// rejects the attribute there); only populated when the schema
274    /// was compiled under [`SchemaVersion::Xsd11`].
275    pub not_qnames:       Vec<QName>,
276    /// XSD 1.1 § 3.10.4 `notNamespace` — namespaces excluded from
277    /// the wildcard's match set.  Shape matches
278    /// [`NamespaceConstraint::List`] — `None` entries are the
279    /// no-namespace (`##local`); `##targetNamespace` is resolved
280    /// at parse time.
281    pub not_namespaces:   Vec<Option<Arc<str>>>,
282    /// XSD 1.1 § 3.10.4 — the `##defined` token in a `notQName`
283    /// list, meaning "any element/attribute with a top-level
284    /// declaration in this schema."  Enforced at validation time:
285    /// element wildcards consult the schema's element table, and
286    /// `xs:anyAttribute` wildcards consult the attribute table.
287    pub not_qname_defined:         bool,
288    /// XSD 1.1 § 3.10.4 — the `##definedSibling` token, meaning
289    /// "any element/attribute declared as a sibling in the
290    /// enclosing complex type."  Enforced at validation time
291    /// against the type's static element-decl set (for `xs:any`)
292    /// or attribute-use set (for `xs:anyAttribute`).
293    pub not_qname_defined_sibling: bool,
294}
295
296#[derive(Debug, Clone)]
297pub enum NamespaceConstraint {
298    /// `namespace="##any"`.
299    Any,
300    /// `namespace="##other"` — any namespace *except* the schema's
301    /// target namespace and the no-namespace.
302    Other,
303    /// Explicit list.  `None` entries mean the no-namespace
304    /// (`##local`).  `##targetNamespace` is resolved at compile time.
305    List(Vec<Option<Arc<str>>>),
306}
307
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub enum ProcessContents { Strict, Lax, Skip }
310
311// ── notation declaration ─────────────────────────────────────────────────────
312
313#[derive(Debug)]
314pub struct NotationDecl {
315    pub name:      QName,
316    pub public_id: Option<String>,
317    pub system_id: Option<String>,
318}
319
320// ── type reference (simple/complex bridge) ───────────────────────────────────
321
322/// A type reference — either a simple or complex type.  Used everywhere
323/// an element's `type=...` is resolved.
324#[derive(Debug, Clone)]
325pub enum TypeRef {
326    Simple(Arc<SimpleType>),
327    Complex(Arc<ComplexType>),
328}
329
330// ── compiled schema ──────────────────────────────────────────────────────────
331
332/// The top-level compiled schema.  Cheap to clone (one `Arc` bump).
333///
334/// All names are fully resolved at this point; the validator looks up
335/// types and elements by [`QName`].
336#[derive(Clone)]
337pub struct Schema {
338    inner: Arc<SchemaInner>,
339}
340
341impl std::fmt::Debug for Schema {
342    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343        f.debug_struct("Schema")
344            .field("target_namespace", &self.inner.target_namespace)
345            .field("elements",   &self.inner.elements.keys().collect::<Vec<_>>())
346            .field("types",      &self.inner.types.keys().collect::<Vec<_>>())
347            .field("attributes", &self.inner.attributes.keys().collect::<Vec<_>>())
348            .finish()
349    }
350}
351
352#[allow(dead_code)] // attribute_groups / model_groups / notations consumed by PR3+
353pub(crate) struct SchemaInner {
354    pub target_namespace: Option<Arc<str>>,
355    pub elements:         HashMap<QName, Arc<ElementDecl>>,
356    pub attributes:       HashMap<QName, Arc<AttributeDecl>>,
357    pub types:            HashMap<QName, TypeRef>,
358    pub attribute_groups: HashMap<QName, Arc<AttributeGroup>>,
359    pub model_groups:     HashMap<QName, Arc<ModelGroup>>,
360    pub notations:        HashMap<QName, Arc<NotationDecl>>,
361    /// For each substitution-group head, the list of element decls
362    /// substitutable into it (computed at compile time from each
363    /// element's `substitutionGroup` attribute).
364    pub substitutions:    HashMap<QName, Vec<Arc<ElementDecl>>>,
365}
366
367impl Schema {
368    pub(crate) fn from_inner(inner: SchemaInner) -> Self {
369        Self { inner: Arc::new(inner) }
370    }
371
372    /// Look up a top-level element by qualified name.
373    pub fn element(&self, name: &QName) -> Option<&Arc<ElementDecl>> {
374        self.inner.elements.get(name)
375    }
376
377    /// Look up a top-level attribute by qualified name.
378    pub fn attribute(&self, name: &QName) -> Option<&Arc<AttributeDecl>> {
379        self.inner.attributes.get(name)
380    }
381
382    /// Look up a top-level type by qualified name.  Returns `None` for
383    /// types not declared in this schema (callers handle built-ins via
384    /// the [`BuiltinType`](super::types::BuiltinType) lookup).
385    pub fn type_def(&self, name: &QName) -> Option<&TypeRef> {
386        self.inner.types.get(name)
387    }
388
389    pub fn target_namespace(&self) -> Option<&str> {
390        self.inner.target_namespace.as_deref()
391    }
392
393    /// Iterate every top-level element.  Useful for instance validation
394    /// to find a candidate root type.
395    pub fn elements(&self) -> impl Iterator<Item = (&QName, &Arc<ElementDecl>)> {
396        self.inner.elements.iter()
397    }
398
399    /// Iterate every top-level type definition.
400    pub fn types(&self) -> impl Iterator<Item = (&QName, &TypeRef)> {
401        self.inner.types.iter()
402    }
403
404    /// Substitution-group members for a given head, if any.  Empty when
405    /// no element substitutes for this head.
406    pub fn substitutes_for(&self, head: &QName) -> &[Arc<ElementDecl>] {
407        self.inner.substitutions.get(head).map(|v| v.as_slice()).unwrap_or(&[])
408    }
409}
410
411// ── grouping declarations ────────────────────────────────────────────────────
412
413/// `<xs:attributeGroup>` — a named bundle of attribute uses, referenced
414/// from complex types via `<xs:attributeGroup ref="…"/>`.
415#[derive(Debug)]
416pub struct AttributeGroup {
417    pub name:       QName,
418    pub attributes: Vec<AttributeUse>,
419    pub any:        Option<Wildcard>,
420}
421
422/// `<xs:group>` — a named model-group particle, referenced from complex
423/// types or other groups.
424#[derive(Debug)]
425pub struct ModelGroup {
426    pub name:     QName,
427    pub particle: Particle,
428}