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}