Skip to main content

sup_xml_core/xsd/
parser.rs

1//! XSD parser — turns one or more XSD source documents into a compiled
2//! [`Schema`].
3//!
4//! Architecture:
5//!
6//! 1. **Pass 1.**  Walk each XSD as XML with [`XmlReader`].  Build a
7//!    shared [`Builder`] that accumulates top-level declarations across
8//!    all included/imported files.  Built-in `xs:*` type references are
9//!    resolved at parse time; user-defined references stay as
10//!    placeholders the validator looks up at instance time.
11//! 2. **Recursive composition.**  `<xs:import>` and `<xs:include>` are
12//!    handled via the configured [`SchemaResolver`].  Cycles are
13//!    silently skipped via the builder's `loaded` set; recursion is
14//!    bounded at [`MAX_INCLUDE_DEPTH`] levels.
15//! 3. **Substitution-group resolution.**  At [`Builder::into_schema`],
16//!    each element's `substitutionGroup` attribute is collected into a
17//!    head → members map for fast O(1) substitution lookup at
18//!    validation time.
19//!
20//! v1 limitations (documented; tracked for follow-ups):
21//! * `<xs:redefine>` not yet supported (use `<xs:include>` instead —
22//!   redefine adds modify-on-import semantics that need careful design).
23//! * Identity constraints (`<xs:key>`/`<xs:keyref>`/`<xs:unique>`)
24//!   parsed but not enforced at validation time (in-flight).
25//! * Substitution-group `block`/`final` flags collected but not
26//!   enforced.
27
28use std::collections::{HashMap, HashSet};
29use std::path::Path;
30use std::sync::Arc;
31
32use crate::reader::{Attr, EventInto, XmlReader};
33
34use super::error::SchemaCompileError;
35use super::facets::{Bound, Facet, FacetSet};
36use super::resolver::{FsResolver, NoResolver, SchemaResolver};
37use super::schema::{
38    AttributeDecl, AttributeGroup, AttributeUse, AttributeUseKind, BlockSet, ContentModel,
39    ElementDecl, GroupKind, MaxOccurs, ModelGroup, NamespaceConstraint, NotationDecl, Particle,
40    ProcessContents, QName, Schema, SchemaInner, SchemaOptions, SchemaVersion, Term, TypeRef,
41    Wildcard,
42};
43use super::types::{
44    BuiltinType, ComplexType, Derivation, DerivationMethod, SimpleType,
45};
46use super::whitespace::WhitespaceMode;
47
48const MAX_INCLUDE_DEPTH: u32 = 64;
49
50// ── public entry points ──────────────────────────────────────────────────────
51
52impl Schema {
53    /// Compile a schema from a single XSD source string.  Equivalent to
54    /// [`compile_with`](Self::compile_with) using a [`NoResolver`] —
55    /// any `<xs:import>` or `<xs:include>` directive is rejected.
56    pub fn compile_str(xsd: &str) -> Result<Schema, SchemaCompileError> {
57        Self::compile_with(xsd, NoResolver)
58    }
59
60    /// Compile a schema with explicit [`SchemaOptions`] (the version
61    /// knob today; more options may land here).  The resolver is
62    /// [`NoResolver`] — imports / includes are rejected.
63    pub fn compile_str_with_options(
64        xsd: &str,
65        options: SchemaOptions,
66    ) -> Result<Schema, SchemaCompileError> {
67        Self::compile_with_options(xsd, NoResolver, options)
68    }
69
70    /// Compile a schema, resolving any `<xs:import>` / `<xs:include>` /
71    /// `<xs:redefine>` directives via the supplied [`SchemaResolver`].
72    ///
73    /// Cycles are detected (a schema importing one that already loaded
74    /// is silently skipped); recursion is bounded at 64 levels deep.
75    pub fn compile_with<R: SchemaResolver>(
76        xsd: &str,
77        resolver: R,
78    ) -> Result<Schema, SchemaCompileError> {
79        Self::compile_with_options(xsd, resolver, SchemaOptions::default())
80    }
81
82    /// Compile a schema with both a [`SchemaResolver`] and explicit
83    /// [`SchemaOptions`].  This is the most general entry point; the
84    /// others all delegate here.
85    pub fn compile_with_options<R: SchemaResolver>(
86        xsd: &str,
87        resolver: R,
88        options: SchemaOptions,
89    ) -> Result<Schema, SchemaCompileError> {
90        let mut builder = Builder::default();
91        // Seed both the static option and the live "effective" copy.
92        // `Auto` mode may later promote `effective_version` to Xsd11
93        // when `parse_schema` sees `vc:minVersion="1.1"` on the root.
94        builder.effective_version = options.version;
95        builder.options = options;
96        parse_one_file(xsd, &mut builder, &resolver, None, /*is_import=*/false)?;
97        builder.into_schema()
98    }
99
100    /// Compile a schema from a file on disk, with relative-path
101    /// resolution of `<xs:import>` and `<xs:include>` against the
102    /// schema's own directory.
103    ///
104    /// Equivalent to:
105    /// ```ignore
106    /// let dir = path.parent().unwrap_or_else(|| Path::new("."));
107    /// Schema::compile_with(&fs::read_to_string(path)?, FsResolver::new(dir))
108    /// ```
109    pub fn compile_file(path: impl AsRef<Path>) -> Result<Schema, SchemaCompileError> {
110        let path = path.as_ref();
111        let xsd = std::fs::read_to_string(path).map_err(|e|
112            SchemaCompileError::msg(format!("read {}: {e}", path.display())))?;
113        let dir = path.parent().unwrap_or_else(|| Path::new("."));
114        Self::compile_with(&xsd, FsResolver::new(dir))
115    }
116}
117
118// ── shared builder (accumulates declarations across nested imports) ──────────
119
120#[derive(Default)]
121struct Builder {
122    /// Compile-time knobs (XSD version, ...).  Copied verbatim from
123    /// the `Schema::compile_*_with_options` caller; the version field
124    /// gates whether 1.1-only constructs are accepted.
125    options:       SchemaOptions,
126    /// Effective XSD version *after* applying `vc:minVersion` auto-
127    /// detect, if [`SchemaVersion::Auto`] is selected.  Populated on
128    /// first `<xs:schema>` parse.  All version-gated checks should
129    /// read from this, not `options.version`.
130    effective_version: SchemaVersion,
131    target_ns:     Option<Arc<str>>,
132    elements:      HashMap<QName, Arc<ElementDecl>>,
133    attributes:    HashMap<QName, Arc<AttributeDecl>>,
134    types:         HashMap<QName, TypeRef>,
135    attr_groups:   HashMap<QName, Arc<AttributeGroup>>,
136    model_groups:  HashMap<QName, Arc<ModelGroup>>,
137    notations:     HashMap<QName, Arc<NotationDecl>>,
138    substitutions: HashMap<QName, Vec<Arc<ElementDecl>>>,
139    /// `schemaLocation` strings already loaded — cycle detection.
140    loaded:        HashSet<String>,
141    /// Current import/include nesting depth.
142    depth:         u32,
143    /// QNames seen as `<xs:attribute ref="...">` for cross-kind
144    /// validation at finalize time (XSD §3.2.3): every ref must
145    /// resolve to a top-level attribute, never an attributeGroup,
146    /// type, or element.
147    pending_attribute_refs: Vec<QName>,
148    /// QNames seen as `<xs:element ref="...">` — must resolve to a
149    /// top-level element at finalize, never a type or attribute.
150    pending_element_refs:   Vec<QName>,
151    /// QNames seen as `<xs:attributeGroup ref="...">` inside another
152    /// <xs:attributeGroup> body.  The complex-type sites are covered
153    /// by `ComplexType::pending_attribute_group_refs`; this collects
154    /// the analogue for top-level attributeGroup definitions.
155    pending_ag_refs_in_ag:  Vec<QName>,
156    /// QNames seen as `<xs:group ref="...">` — must resolve to a
157    /// top-level model group.
158    #[allow(dead_code)]
159    pending_group_refs:     Vec<QName>,
160    /// Names of model groups defined inside an `<xs:redefine>` body.
161    /// Per XSD §4.2.2 these are allowed to contain exactly one self-
162    /// reference (`<xs:group ref="X">` where X is the redefined name),
163    /// so they're exempted from the direct-cycle check.
164    redefined_groups:       HashSet<QName>,
165    /// Captures each `<xs:redefine>` group / attributeGroup body so the
166    /// post-resolution pass can verify the redefining content is a
167    /// valid restriction of the pre-redefine original (src-redefine).
168    /// Each entry is `(name, redefining particle, pre-redefine particle)`.
169    redefined_group_originals: Vec<(QName, Particle, Particle)>,
170    /// Same shape, for `<xs:attributeGroup>` redefinitions: a snapshot
171    /// of the original's attribute uses paired with the redefining
172    /// arc so the post-pass can validate src-redefine restriction.
173    /// The fourth tuple element records whether the redefining body
174    /// contained a `<xs:attributeGroup ref="same-name"/>` self-ref —
175    /// when it does, the redefining is an *additive* shape (the
176    /// original's attributes are spliced in via the ref) rather than
177    /// a strict restriction, and the validation rules relax to
178    /// match what Saxon / Xerces / MSXML accept.
179    redefined_attr_group_originals: Vec<(QName, Arc<AttributeGroup>, Arc<AttributeGroup>, bool)>,
180    /// Set by `handle_redefine` while parsing a redefining
181    /// attributeGroup body; consulted by `parse_top_attribute_group`
182    /// when it sees an `<xs:attributeGroup ref="…">` so a same-name
183    /// reference can be recorded as a self-reference.
184    redefining_ag_name: Option<QName>,
185    /// Populated when `redefining_ag_name` matches the inner ref.
186    /// Read once by `handle_redefine` after `parse_top_attribute_group`
187    /// returns, then cleared.
188    redefining_ag_saw_self_ref: bool,
189    /// Namespaces brought into scope via `<xs:import>`.  `None` means
190    /// the no-namespace was imported (`<xs:import>` without a
191    /// `namespace=` attribute).  Used by the post-parse ref-resolution
192    /// passes to flag references to foreign namespaces the schema
193    /// didn't import — XSD §3.3.6 / §3.2.3 src-resolve clause 4 require
194    /// every QName reference to a foreign namespace's component to be
195    /// preceded by an import of that namespace.
196    imported_namespaces:    HashSet<Option<Arc<str>>>,
197    /// Captures simple-type restrictions whose declared `base="…"`
198    /// pointed at a same-target-namespace type that wasn't yet
199    /// resolved at parse time (forward reference).  Each entry holds
200    /// the base's QName plus the *new* facets this restriction added,
201    /// so the post-pass can fetch the resolved base, splice in its
202    /// facet set, and re-run `check_facet_tightening` — catching
203    /// derived bounds that loosen the base (XSD §4.3 cvc-restriction).
204    pending_simple_facet_checks: Vec<(QName, Vec<Facet>)>,
205    /// Adjacency list of `<xs:attributeGroup ref="...">` edges keyed
206    /// by the owning attributeGroup's QName.  Used to detect cycles
207    /// (XSD §3.6.6 src-attribute-group-cyclic) — refs from inside
208    /// complex types live in `ComplexType::pending_attribute_group_refs`
209    /// and don't participate in cycles because complex types can't
210    /// be a target of an attributeGroup ref.
211    ag_refs_by_owner:       HashMap<QName, Vec<QName>>,
212}
213
214impl Builder {
215    fn into_schema(mut self) -> Result<Schema, SchemaCompileError> {
216        // Reject `<xs:attributeGroup ref="X">` references whose X
217        // resolves to a different kind of schema component (a type,
218        // an element, an attribute). Runs *before* the rewrite pass
219        // because the rewrite clears the pending-refs list.
220        check_attribute_group_ref_kinds(&self.types, &self.elements,
221                                        &self.attr_groups, &self.attributes)?;
222
223        // Same kind-collision check for refs that appear inside
224        // <xs:attributeGroup> bodies (not just inside complex types).
225        check_ag_refs_in_ag(&self.pending_ag_refs_in_ag, &self.attr_groups,
226                            &self.types, &self.elements, &self.attributes)?;
227
228        // XSD §3.6.6 (src-attribute-group-cyclic) — an attribute
229        // group's expansion must not (transitively) reference
230        // itself.
231        check_attribute_group_cycles(&self.ag_refs_by_owner)?;
232
233        // Every `<xs:attribute ref="X">` must point at a top-level
234        // <xs:attribute> declaration — not an attributeGroup, complex
235        // type, or element (XSD §3.2.3). Tolerate unresolvable names
236        // for cross-schema soft-skip; flag cross-kind collisions.
237        check_attribute_refs(&self.pending_attribute_refs, &self.attributes,
238                             &self.attr_groups, &self.types, &self.elements,
239                             self.target_ns.as_deref(), &self.imported_namespaces)?;
240
241        // Every `<xs:element ref="X">` must resolve to a top-level
242        // element declaration — never a type, attribute, or other
243        // schema component (XSD §3.3.3).  Foreign-namespace refs
244        // additionally require the namespace to have been imported
245        // (XSD §3.3.6 src-resolve clause 4).
246        check_element_refs(&self.pending_element_refs, &self.elements,
247                           &self.types, &self.attributes, &self.attr_groups,
248                           self.target_ns.as_deref(), &self.imported_namespaces)?;
249
250        // Every `<xs:element substitutionGroup="X">` head must
251        // resolve to an existing top-level element decl.
252        check_substitution_group_heads(&self.elements)?;
253        // XSD §3.3.6 — a substituting element's type must derive
254        // from the head element's type using a method that is not
255        // blocked by the head's (or head type's) `final`.
256        check_substitution_group_typing(&self.elements, &self.types)?;
257
258        // XSD §3.3.6 — an element typed `xs:ID` (or any simple type
259        // derived from it) cannot have a `default=` / `fixed=` value
260        // constraint.  The inline check during parse can miss this
261        // when the element's type is a forward reference whose
262        // builtin lineage isn't known yet; a post-pass over the
263        // resolved type map catches the strays.
264        check_id_typed_element_value_constraints(&self.elements, &self.types)?;
265
266        // Every `type="X"` reference whose `X` matches another kind
267        // of schema component (attribute, attributeGroup, element)
268        // must be flagged — `type=` is a type-only namespace.
269        check_type_refs_collide(&self.types, &self.attributes,
270                                &self.attr_groups, &self.elements)?;
271
272        // Attribute groups can reference other attribute groups by
273        // name (XSD §3.6.3).  When attg1 declares `<xs:attributeGroup
274        // ref="attg2"/>` and attg2 is declared later in source order,
275        // the parser captures attg2's name in
276        // [`Builder::ag_refs_by_owner`] but copies no attributes
277        // (attg2 wasn't built yet).  Walk the dependency graph
278        // bottom-up here so each group's attribute list contains the
279        // transitive union of every group it references — otherwise
280        // a complex type referencing attg1 would see attg1's local
281        // attributes only, and instances using attg2's attributes
282        // would be wrongly rejected with "unexpected attribute".
283        flatten_nested_attribute_group_refs(
284            &mut self.attr_groups, &self.ag_refs_by_owner,
285        );
286        // Expand pending `<xs:attributeGroup ref="…"/>` references
287        // captured at parse time (forward refs to groups declared
288        // later in source order).  Runs before extension merging so
289        // a derived type's inherited attributes include the resolved
290        // group members.
291        resolve_attribute_group_refs(&mut self.types, &mut self.elements, &self.attr_groups)?;
292
293        // XSD §3.8.6 src-model-group — a group must not transitively
294        // reference itself except via an element boundary.  Detect the
295        // direct (element-free) cycles before group expansion, since
296        // the expander quietly leaves recursive refs in place and the
297        // matcher builder cannot tell direct cycles from element-
298        // mediated ones.
299        check_model_group_cycles(&self.model_groups, &self.redefined_groups)?;
300
301        // Expand `<xs:group ref="...">` particles into the referenced
302        // model group's particle.  Runs before any other pass that
303        // walks the content tree.  Group refs may chain (a group
304        // references another group), so this iterates to fixpoint.
305        resolve_group_refs(&mut self.types, &mut self.elements, &self.model_groups)?;
306
307        // Patch `<xs:element ref="...">` placeholders in every
308        // complex type's content model — both top-level named types
309        // and the inline anonymous types attached to top-level element
310        // decls.  The parser produces a stand-in ElementDecl
311        // (type=xs:string) for refs; this pass swaps in the real
312        // top-level decl from `self.elements`.
313        //
314        // Runs before substitution-group / extension-merge / matcher
315        // compilation so the DFA records the correct decl Arc.
316        resolve_element_refs(&mut self.types, &mut self.elements);
317
318        // cos-ct-extends / cos-ct-restricts (XSD §3.4.6) — the
319        // derived type's content nature (simple vs complex) must
320        // match the base's. Runs before extension merging so the
321        // derived content still reflects exactly what the user
322        // wrote.
323        check_derivation_content_kind(&self.types)?;
324
325        // XSD §3.4.6 — for complexContent derivation, the derived
326        // type's `mixed` value must match the base's.  Restriction
327        // can't widen text-acceptance, and extension can't change
328        // the content-model kind.  Done before extension merging so
329        // we see the derived's intended mixed flag.
330        check_complex_mixed_consistency(&self.types, &self.elements)?;
331
332        // XSD §3.4.6 — a base complex type's `final` may forbid the
333        // derived type's chosen derivation method (restriction or
334        // extension).  Enforce before merge so the spec's intended
335        // base is the one whose `final` we check.
336        check_complex_type_final(&self.types, &self.elements)?;
337
338        // Merge content+attributes from base into every named
339        // complex type that derives by extension.  Done before
340        // matcher compilation so the DFA is built over the merged
341        // content model.
342        merge_extension_chains(&mut self.types)?;
343
344        // Resolve `UNRESOLVED:` placeholders inside union member lists and
345        // list item types to their real (forward-referenced) simple types.
346        // Without this, a union member defined later in the schema keeps
347        // its String-defaulted placeholder and the union over-accepts.
348        resolve_simple_member_placeholders(&mut self.types);
349
350        // Inline anonymous complex types attached to top-level
351        // element decls (e.g. `<xs:element name="Foo"><xs:complexType>
352        // <xs:complexContent><xs:extension base="Bar">…`) need the
353        // same extension-merge treatment, but they live in
354        // `self.elements[].type_def`, not in `self.types`.  Walk the
355        // element map and patch each one whose inline type derives by
356        // extension.  Runs AFTER named types are merged so the base
357        // lookup sees their fully composed content; runs BEFORE the
358        // substitution-group map is captured below so that map
359        // records the patched Arcs.
360        merge_inline_extension_in_elements(&mut self.elements, &self.types);
361
362        // XSD §3.4.2 — a restriction-derived complex type implicitly
363        // inherits any base attribute uses it doesn't redeclare.
364        // Without this fold-in the derived type would silently drop
365        // those attributes from its effective set.
366        merge_restriction_attributes(&mut self.types, &mut self.elements);
367
368        // Element refs in other types' content (e.g. `doc → element
369        // ref=elem`) were patched against the pre-merge element decl
370        // by the earlier `resolve_element_refs` pass.  Re-run after
371        // the inline-extension merge so consumers see the merged
372        // type def, not the stale one captured before composition.
373        resolve_element_refs(&mut self.types, &mut self.elements);
374
375        // Particle-restriction soundness check (cvc-particle-restricts,
376        // XSD §3.9.6). Runs AFTER extension chains are merged so the
377        // base type's content already incorporates any inherited
378        // particles. Restriction-derived types are untouched by
379        // `merge_extension_chains`, so the derived particle still
380        // reflects exactly what the user wrote.
381        // XSD §3.8.6 — within a content model, two element particles
382        // with the same name cannot carry different type definitions.
383        check_element_decls_consistent(&self.types, &self.elements)?;
384
385        // XSD §4.3 cvc-restriction — for simple-type restrictions
386        // whose `base="…"` was a same-namespace forward reference at
387        // parse time, the inherited facets weren't known yet, so the
388        // inline check ran against an empty base set.  Resolve the
389        // base now and revalidate the derived facets against the
390        // real ancestor facet set.
391        check_pending_simple_facet_tightening(
392            &self.pending_simple_facet_checks, &self.types,
393        )?;
394
395        super::particle_restriction::check_restriction_chains(&self.types, &self.elements,
396                                                               self.target_ns.as_deref())?;
397
398        // XSD §4.2.2 src-redefine — each redefined <xs:group>'s new
399        // body (with any self-reference expanded back to the original
400        // group's particle) must be a valid restriction of the
401        // pre-redefine original.  Captured during the redefine body
402        // walk; checked here once the types map is fully resolved.
403        super::particle_restriction::check_redefined_groups(
404            &self.redefined_group_originals, &self.types, &self.elements,
405            self.target_ns.as_deref(),
406        )?;
407        // Same idea for `<xs:attributeGroup>` redefinitions —
408        // honoring the spec-allowed additive shape when the
409        // redefining body contains a `<xs:attributeGroup
410        // ref="same-name"/>` self-reference.
411        check_redefined_attribute_groups(&self.redefined_attr_group_originals,
412                                         &self.types)?;
413
414        // XSD §3.2.6 / §3.4.6 — every <xs:attribute>'s `type=`
415        // must resolve to a simple type, never a complex type.
416        // Pointed-to references are placeholders post-parse;
417        // verify them against the resolved type map here.
418        check_attribute_type_kinds(&self.types, &self.attributes, &self.elements)?;
419
420        // XSD §3.4.6 (cos-ct-derived-ok by restriction) — every
421        // base attribute use whose `use=required` must be retained
422        // as required in the restricting derived type, and base
423        // `fixed="X"` values cannot be redefined to differ.
424        check_complex_restriction_attributes(&self.types, &self.elements)?;
425
426        // XSD §3.11.6 — every <xs:keyref refer="…"> must name a
427        // <xs:key> or <xs:unique> declared somewhere in the schema.
428        check_keyref_refer(&self.elements)?;
429
430
431        // XSD §3.3.3 (cvc-elt-2.x) — element's default/fixed value
432        // is only valid against a simple or mixed-complex type. The
433        // parser already enforces this for inline types; this
434        // post-pass catches type-by-name references whose resolved
435        // type wasn't available at parse time.
436        check_element_value_constraints(&self.types, &self.elements)?;
437
438        // Compute substitution-group map from each element's
439        // `substitutionGroup` attribute (if any), then take the
440        // transitive closure: if `A substitutes B` and `B substitutes C`,
441        // then `A` also substitutes `C`.  XSD §3.3.6 treats
442        // substitution-group membership as transitive.
443        let mut subs: HashMap<QName, Vec<Arc<ElementDecl>>> = HashMap::new();
444        for decl in self.elements.values() {
445            if let Some(head) = &decl.substitution_group {
446                subs.entry(head.clone()).or_default().push(decl.clone());
447            }
448        }
449        // Closure: for each member of subs[A], also add subs[member.name]
450        // recursively.  Bounded iteration to handle declaration order.
451        let mut changed = true;
452        let mut guard = 0;
453        while changed && guard < 64 {
454            changed = false;
455            guard += 1;
456            let heads: Vec<QName> = subs.keys().cloned().collect();
457            for head in heads {
458                let direct: Vec<Arc<ElementDecl>> = subs[&head].clone();
459                for m in direct {
460                    let Some(deeper) = subs.get(&m.name).cloned() else { continue };
461                    for d in deeper {
462                        let list = subs.get_mut(&head).unwrap();
463                        if !list.iter().any(|x| Arc::ptr_eq(x, &d) || x.name == d.name) {
464                            list.push(d);
465                            changed = true;
466                        }
467                    }
468                }
469            }
470        }
471        self.substitutions = subs;
472
473        // Compile content matchers (DFA where possible, all-group
474        // fallback otherwise) for every reachable complex type — both
475        // top-level *and* inline anonymous types attached to element
476        // decls.  Done here so substitution groups are already resolved.
477        let mut visited: HashSet<usize> = HashSet::new();
478        let types_snapshot = self.types.clone();
479        let target_ns_str = self.target_ns.as_deref().map(|s| s.to_owned());
480        let target_ns_ref = target_ns_str.as_deref();
481        for tr in self.types.values() {
482            if let TypeRef::Complex(ct) = tr {
483                walk_complex_for_matchers(ct, &self.substitutions, &types_snapshot,
484                                          &mut visited, target_ns_ref)?;
485            }
486        }
487        for decl in self.elements.values() {
488            if let TypeRef::Complex(ct) = &decl.type_def {
489                walk_complex_for_matchers(ct, &self.substitutions, &types_snapshot,
490                                          &mut visited, target_ns_ref)?;
491            }
492        }
493
494        Ok(Schema::from_inner(SchemaInner {
495            target_namespace: self.target_ns,
496            elements:         self.elements,
497            attributes:       self.attributes,
498            types:            self.types,
499            attribute_groups: self.attr_groups,
500            model_groups:     self.model_groups,
501            notations:        self.notations,
502            substitutions:    self.substitutions,
503        }))
504    }
505}
506
507/// Top-level driver — parse one file's contents into the shared builder.
508/// Recursive on `<xs:import>` / `<xs:include>` via the resolver.
509fn parse_one_file<R: SchemaResolver>(
510    xsd: &str,
511    builder: &mut Builder,
512    resolver: &R,
513    expected_target_ns: Option<&str>,
514    is_import: bool,
515) -> Result<(), SchemaCompileError> {
516    if builder.depth >= MAX_INCLUDE_DEPTH {
517        return Err(SchemaCompileError::msg(format!(
518            "schema include nesting exceeded {MAX_INCLUDE_DEPTH} levels"
519        )));
520    }
521    let mut p = Parser::new(xsd, builder, resolver, expected_target_ns);
522    p.is_import_target = is_import;
523    p.parse_schema()
524}
525
526// ── internal parser ──────────────────────────────────────────────────────────
527
528struct Parser<'a, 'b, R: SchemaResolver> {
529    reader:    XmlReader<'a>,
530    builder:   &'b mut Builder,
531    resolver:  &'b R,
532    /// If `Some`, the schema being parsed must have this `targetNamespace`
533    /// (used to enforce `<xs:include>` rules — included schemas must
534    /// either match or be unqualified).
535    expected_target_ns: Option<&'b str>,
536    /// True when this parser was spawned from `<xs:import>` (vs
537    /// `<xs:include>` or `<xs:redefine>`).  Imports require the
538    /// loaded file's `targetNamespace` to match `expected_target_ns`
539    /// exactly; includes apply the chameleon rule when the file has
540    /// no `targetNamespace`.
541    is_import_target: bool,
542    /// The `targetNamespace` of the schema document being parsed.
543    /// Per-file (NOT the same as `builder.target_ns`, which holds
544    /// the original/root schema's namespace).  An imported schema
545    /// may declare its own namespace; declarations parsed from that
546    /// file land in this namespace, not the importer's.
547    current_target_ns: Option<Arc<str>>,
548    /// `elementFormDefault` for the schema document being parsed.
549    /// Per-file: an included schema with its own form default doesn't
550    /// inherit the includer's.
551    element_form_default: Form,
552    /// `attributeFormDefault` for the schema document being parsed.
553    attribute_form_default: Form,
554    /// `blockDefault` — applied to top-level `<xs:element>` and
555    /// `<xs:complexType>` decls without their own `block` attribute.
556    /// XSD 1.0 §3.1.
557    block_default: BlockSet,
558    /// `finalDefault` — same idea for `final`.
559    final_default: BlockSet,
560    ns_stack:  Vec<HashMap<String, String>>,
561    attr_buf:  Vec<Attr<'a>>,
562    /// Track every `id` value seen so far in this schema document so
563    /// duplicates (XSD spec: `id` is typed `xs:ID` and must be unique
564    /// within the document) and ill-formed NCNames are rejected at
565    /// compile time.
566    seen_ids:  HashSet<String>,
567    /// Identity-constraint names (XSD §3.11.1): unique, key, and
568    /// keyref share a single namespace, so a duplicate name across
569    /// any combination is a schema validity error.
570    seen_ic_names: HashSet<QName>,
571    /// Top-level element names declared *in this schema document*.
572    /// Per-file rather than per-compilation so that include cycles
573    /// re-processing the same source don't trip the duplicate check.
574    local_top_element_names: HashSet<QName>,
575    /// True while parsing an `<xs:redefine>` body — redefinitions
576    /// legitimately re-declare names that already exist in the
577    /// builder, so per-name duplicate checks must be relaxed.
578    in_redefine: bool,
579    /// Most recently parsed `<xs:restriction base=…>` resolved QName.
580    /// Captured during `parse_simple_restriction` so the redefine-body
581    /// validator can confirm that a redefining `<xs:simpleType>`
582    /// restricts the same-named original (src-redefine).
583    last_simple_restriction_base: Option<QName>,
584    /// The QName of a top-level `<xs:simpleType>` currently being
585    /// parsed.  `parse_simple_restriction` consults it to reject a
586    /// direct self-reference (`base="X"` inside `<xs:simpleType name="X">`)
587    /// when not inside `<xs:redefine>`.
588    simple_type_in_flight: Option<QName>,
589}
590
591#[derive(Debug, Clone, Copy, PartialEq, Eq)]
592enum Form { Qualified, Unqualified }
593
594impl Form {
595    fn parse(s: &str) -> Option<Self> {
596        match s {
597            "qualified"   => Some(Form::Qualified),
598            "unqualified" => Some(Form::Unqualified),
599            _ => None,
600        }
601    }
602}
603
604const XS: &str = "http://www.w3.org/2001/XMLSchema";
605
606impl<'a, 'b, R: SchemaResolver> Parser<'a, 'b, R> {
607    fn new(
608        input: &'a str,
609        builder: &'b mut Builder,
610        resolver: &'b R,
611        expected_target_ns: Option<&'b str>,
612    ) -> Self {
613        Self {
614            reader:   XmlReader::from_str(input),
615            builder,
616            resolver,
617            expected_target_ns,
618            is_import_target: false,
619            // Set by `parse_schema` from the root's `targetNamespace`
620            // attribute (or from `expected_target_ns` for chameleon
621            // includes that don't declare one).
622            current_target_ns: None,
623            // Spec default for both `*FormDefault` attributes is
624            // "unqualified".  Overridden when the `<xs:schema>` root
625            // declares a value (read in `parse_schema`).
626            element_form_default:   Form::Unqualified,
627            attribute_form_default: Form::Unqualified,
628            block_default: BlockSet::default(),
629            final_default: BlockSet::default(),
630            ns_stack: vec![{
631                // Namespaces in XML §3 — the `xml` prefix is bound
632                // implicitly to the XML namespace URI, with no
633                // explicit declaration required.
634                let mut m = HashMap::new();
635                m.insert("xml".to_string(), "http://www.w3.org/XML/1998/namespace".to_string());
636                m
637            }],
638            attr_buf: Vec::new(),
639            seen_ids: HashSet::new(),
640            seen_ic_names: HashSet::new(),
641            local_top_element_names: HashSet::new(),
642            in_redefine: false,
643            last_simple_restriction_base: None,
644            simple_type_in_flight: None,
645        }
646    }
647
648    /// Drain the staged attribute buffer into an owned `Vec`, also
649    /// running schema-wide attribute checks that fire on every
650    /// element regardless of which parse method consumes it
651    /// (currently `id` uniqueness/NCName and `name` NCName).
652    fn take_attrs(&mut self) -> Result<Vec<Attr<'a>>, SchemaCompileError> {
653        let attrs: Vec<Attr<'a>> = self.attr_buf.drain(..).collect();
654        self.validate_id_attr(&attrs)?;
655        self.validate_name_attr(&attrs)?;
656        Ok(attrs)
657    }
658
659    fn validate_id_attr(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
660        let Some(id) = self.attr(attrs, "id") else { return Ok(()) };
661        super::types::SimpleType::of_builtin(super::types::BuiltinType::Id)
662            .validate(id)
663            .map_err(|e| self.err(format!(
664                "id={id:?} is not a valid ID/NCName: {}", e.message
665            )))?;
666        if !self.seen_ids.insert(id.to_owned()) {
667            return Err(self.err(format!("duplicate id {id:?} in schema")));
668        }
669        Ok(())
670    }
671
672    /// All XSD elements that accept a `name` attribute
673    /// (`<xs:element>`, `<xs:attribute>`, `<xs:complexType>`,
674    /// `<xs:simpleType>`, `<xs:group>`, `<xs:attributeGroup>`,
675    /// `<xs:notation>`, `<xs:unique>`, `<xs:key>`, `<xs:keyref>`)
676    /// require it to be an NCName; no XSD-defined element accepts
677    /// a non-NCName `name`, so the check is applied uniformly
678    /// rather than dispatching per parent element.
679    fn validate_name_attr(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
680        let Some(name) = self.attr(attrs, "name") else { return Ok(()) };
681        super::types::SimpleType::of_builtin(super::types::BuiltinType::NCName)
682            .validate(name)
683            .map_err(|e| self.err(format!(
684                "name={name:?} is not a valid NCName: {}", e.message
685            )))?;
686        Ok(())
687    }
688
689    fn err(&self, msg: impl Into<String>) -> SchemaCompileError {
690        SchemaCompileError::msg(msg)
691    }
692
693    /// Parse an `xs:boolean` attribute value strictly per XSD §3.2.2.3:
694    /// only `"true"`, `"false"`, `"1"`, `"0"` are valid (case-sensitive,
695    /// no surrounding whitespace).  Returns `Ok(None)` when the attribute
696    /// is absent.
697    fn parse_xsd_bool(
698        &self,
699        attrs: &[Attr<'a>],
700        name:  &str,
701    ) -> Result<Option<bool>, SchemaCompileError> {
702        let Some(raw) = self.attr(attrs, name) else { return Ok(None); };
703        match raw {
704            "true"  | "1" => Ok(Some(true)),
705            "false" | "0" => Ok(Some(false)),
706            _ => Err(self.err(format!(
707                "{name}={raw:?}: must be \"true\", \"false\", \"1\", or \"0\""
708            ))),
709        }
710    }
711
712    /// Enforce XSD's "at most one annotation, must come first" rule
713    /// (§3.x.x for each construct).  Call once per child element
714    /// encountered inside a parent that doesn't allow interleaved
715    /// annotations (most constructs — the exception is `<xs:schema>`
716    /// itself).  Mutates the two state bools to track what's been
717    /// seen so far in this parent's child sequence.
718    fn check_annotation_pos(
719        &self,
720        local: &str,
721        seen_annotation:     &mut bool,
722        seen_non_annotation: &mut bool,
723        parent:              &str,
724    ) -> Result<(), SchemaCompileError> {
725        if local == "annotation" {
726            if *seen_annotation {
727                return Err(self.err(format!(
728                    "<xs:{parent}> may have at most one <xs:annotation> child"
729                )));
730            }
731            if *seen_non_annotation {
732                return Err(self.err(format!(
733                    "<xs:annotation> must precede other children of <xs:{parent}>"
734                )));
735            }
736            *seen_annotation = true;
737        } else {
738            *seen_non_annotation = true;
739        }
740        Ok(())
741    }
742
743    /// Walk a body that only allows at-most-one `<xs:annotation>` and
744    /// nothing else as a direct child (e.g. `<xs:any>`,
745    /// `<xs:anyAttribute>`, leaf facets).  Replaces `skip_body()` at
746    /// sites that should still enforce the annotation rule.  Uses
747    /// the standard push/pop ns-scope walk pattern.
748    fn parse_anno_only_body(&mut self, parent: &str) -> Result<(), SchemaCompileError> {
749        let mut seen_anno = false;
750        let mut seen_other = false;
751        loop {
752            match self.next_event()? {
753                EventInto::StartElement { name } => {
754                    let child_attrs = self.take_attrs()?;
755                    self.push_ns_scope(&child_attrs);
756                    let qn = self.qname_of_element(&name)?;
757                    if qn.namespace.as_deref() == Some(XS) {
758                        self.check_annotation_pos(
759                            &qn.local, &mut seen_anno, &mut seen_other, parent,
760                        )?;
761                        match qn.local.as_ref() {
762                            "annotation" => self.parse_annotation_body(&child_attrs)?,
763                            other => return Err(self.err(format!(
764                                "<xs:{parent}> body may only contain <xs:annotation>, \
765                                 found <xs:{other}>"
766                            ))),
767                        }
768                    } else if qn.namespace.is_none() {
769                        // No-namespace child elements aren't covered by
770                        // the XSD foreign-extension rule (which applies
771                        // to elements in a *different* namespace, not
772                        // the absent one).  The schema-for-schemas
773                        // content model `(annotation?)` doesn't admit
774                        // them — reject.
775                        return Err(self.err(format!(
776                            "<xs:{parent}> body may only contain <xs:annotation>, \
777                             found <{}>", qn.local,
778                        )));
779                    } else {
780                        // True foreign-namespace child: tolerated as
781                        // an extension annotation per XSD §1.4.1.
782                        self.skip_body()?;
783                    }
784                    self.pop_ns_scope();
785                }
786                EventInto::Text(t) | EventInto::CData(t) => {
787                    if !t.chars().all(char::is_whitespace) {
788                        return Err(self.err(format!(
789                            "<xs:{parent}> body has non-whitespace text content"
790                        )));
791                    }
792                }
793                EventInto::EndElement { .. } => return Ok(()),
794                EventInto::Eof => return Err(self.err("unexpected EOF")),
795                _ => {}
796            }
797        }
798    }
799
800    /// Build a [`TypeRef`] for a `type="..."` reference.  Built-in
801    /// `xs:*` types are resolved immediately; user-defined types get a
802    /// placeholder that the post-pass [`resolve_references`] patches.
803    ///
804    /// Resolving built-ins at parse time skips a HashMap lookup per
805    /// element/attribute on the validator's hot path — most schemas
806    /// have built-in types in 80%+ of their fields.
807    fn type_ref_for(&self, type_qn: QName) -> TypeRef {
808        if type_qn.namespace.as_deref() == Some(QName::XSD_NS) {
809            if let Some(b) = BuiltinType::from_name(&type_qn.local) {
810                return TypeRef::Simple(Arc::new(SimpleType::of_builtin(b)));
811            }
812            // xs:anyType isn't in the BuiltinType set (it's a
813            // complex type); build the spec's concrete ur-type so
814            // substitution-group typing checks and xsi:type
815            // resolution see a real ComplexType, not a placeholder.
816            if type_qn.local.as_ref() == "anyType" {
817                return any_type_ref();
818            }
819        }
820        // User-defined type — placeholder, patched in the post-pass.
821        TypeRef::Simple(Arc::new(SimpleType {
822            name:       Some(Arc::from(format!("UNRESOLVED:{type_qn}"))),
823            builtin:    BuiltinType::String,
824            facets:     FacetSet::default(),
825            whitespace: WhitespaceMode::Preserve,
826            variety:    super::types::Variety::Atomic,
827            final_:     super::schema::BlockSet::default(),
828            assertions: Vec::new(),
829        }))
830    }
831
832    /// Same as [`type_ref_for`] but for attribute decls (always
833    /// simple-typed).  Returns `Arc<SimpleType>` directly.
834    fn simple_type_for(&self, type_qn: QName) -> Arc<SimpleType> {
835        if type_qn.namespace.as_deref() == Some(QName::XSD_NS) {
836            if let Some(b) = BuiltinType::from_name(&type_qn.local) {
837                return Arc::new(SimpleType::of_builtin(b));
838            }
839        }
840        Arc::new(SimpleType {
841            name:       Some(Arc::from(format!("UNRESOLVED:{type_qn}"))),
842            builtin:    BuiltinType::String,
843            facets:     FacetSet::default(),
844            whitespace: WhitespaceMode::Preserve,
845            variety:    super::types::Variety::Atomic,
846            final_:     super::schema::BlockSet::default(),
847            assertions: Vec::new(),
848        })
849    }
850
851    // ── namespace bookkeeping ────────────────────────────────────────────
852
853    fn push_ns_scope(&mut self, attrs: &[Attr<'a>]) {
854        let top = self.ns_stack.last().cloned().unwrap_or_default();
855        let mut new = top;
856        for a in attrs {
857            let name = a.name;
858            if name == "xmlns" {
859                new.insert(String::new(), a.value.to_string());
860            } else if let Some(prefix) = name.strip_prefix("xmlns:") {
861                new.insert(prefix.to_string(), a.value.to_string());
862            }
863        }
864        self.ns_stack.push(new);
865    }
866
867    fn pop_ns_scope(&mut self) {
868        self.ns_stack.pop();
869    }
870
871    /// Resolve a prefix against the current scope.  Returns `None` for
872    /// the no-namespace, `Some(uri)` otherwise.
873    fn resolve_prefix(&self, prefix: &str) -> Option<&str> {
874        for scope in self.ns_stack.iter().rev() {
875            if let Some(uri) = scope.get(prefix) {
876                return Some(uri.as_str());
877            }
878        }
879        None
880    }
881
882    /// Parse a `prefix:local` or `local` form into a [`QName`].  When no
883    /// prefix is present, the schema's `targetNamespace` is used unless
884    /// the caller passed a `null_ns_default` of `false` (some XSD
885    /// constructs default unprefixed names to no-namespace).
886    fn parse_qname(&self, raw: &str, null_ns_default: bool) -> Result<QName, SchemaCompileError> {
887        // XML §3 — a QName is `(Prefix ':')? LocalPart`; both prefix
888        // and local part must be NCNames. Reject anything that isn't.
889        let ncname_check = |s: &str, label: &str| -> Result<(), SchemaCompileError> {
890            super::types::SimpleType::of_builtin(super::types::BuiltinType::NCName)
891                .validate(s)
892                .map_err(|e| self.err(format!(
893                    "QName {label} {s:?} is not an NCName: {}", e.message,
894                )))?;
895            Ok(())
896        };
897        match raw.split_once(':') {
898            Some((prefix, local)) => {
899                ncname_check(prefix, "prefix")?;
900                ncname_check(local, "local part")?;
901                let uri = self.resolve_prefix(prefix).ok_or_else(||
902                    self.err(format!("undeclared namespace prefix {prefix:?}"))
903                )?;
904                Ok(QName::new(Some(uri), local))
905            }
906            None => {
907                ncname_check(raw, "local part")?;
908                let uri = if null_ns_default { None } else {
909                    self.resolve_prefix("").map(|s| s.to_owned())
910                        .or_else(|| self.current_target_ns.as_deref().map(|s| s.to_owned()))
911                };
912                Ok(QName {
913                    namespace: uri.map(Arc::from),
914                    local:     Arc::from(raw),
915                })
916            }
917        }
918    }
919
920    /// XSD 1.1 § 3.2.2 — `inheritable="true|false"` on
921    /// `<xs:attribute>`.  Default is `false`.  In 1.0 mode the
922    /// attribute is unknown to the schema-for-schema; we surface
923    /// that explicitly rather than silently ignoring (which would
924    /// be the libxml2-style bug we set out to avoid).
925    fn parse_inheritable(&self, attrs: &[Attr<'a>]) -> Result<bool, SchemaCompileError> {
926        let Some(raw) = self.attr(attrs, "inheritable") else { return Ok(false); };
927        if !matches!(self.builder.effective_version, SchemaVersion::Xsd11) {
928            return Err(self.err(
929                "<xs:attribute inheritable=...> is an XSD 1.1 attribute — \
930                 set SchemaOptions::version to Xsd11, or to Auto with \
931                 vc:minVersion=\"1.1\" on <xs:schema>",
932            ));
933        }
934        match raw {
935            "true"  | "1" => Ok(true),
936            "false" | "0" => Ok(false),
937            other => Err(self.err(format!(
938                "<xs:attribute inheritable={other:?}>: must be \"true\" or \"false\""
939            ))),
940        }
941    }
942
943    /// Build a [`Wildcard`] from an `<xs:any>` / `<xs:anyAttribute>`
944    /// attribute set, using the parser's current namespace context to
945    /// resolve `notQName` tokens against `xmlns:` declarations in scope.
946    fn parse_wildcard(&self, attrs: &[Attr<'a>]) -> Result<Wildcard, SchemaCompileError> {
947        // XSD §3.10.2 — attribute set of <xs:any> / <xs:anyAttribute>.
948        // The minOccurs/maxOccurs are only on <xs:any>; we keep them
949        // in the allow-list here so the same checker handles both
950        // (anyAttribute callers don't supply them).  `notQName` /
951        // `notNamespace` are XSD 1.1 additions; they're allow-listed
952        // unconditionally but `parse_wildcard_attrs` rejects them
953        // in 1.0 mode with a precise error citing the version.
954        self.check_known_attrs(attrs, &[
955            "id", "namespace", "processContents", "minOccurs", "maxOccurs",
956            "notQName", "notNamespace",
957        ], "any")?;
958        parse_wildcard_attrs(
959            attrs,
960            &self.current_target_ns,
961            self.builder.effective_version,
962            &mut |tok| self.parse_qname(tok, false),
963        )
964    }
965
966    // ── event helpers ────────────────────────────────────────────────────
967
968    fn next_event(&mut self) -> Result<EventInto<'a>, SchemaCompileError> {
969        loop {
970            let ev = self.reader.next_into(&mut self.attr_buf)?;
971            match ev {
972                EventInto::Comment(_) | EventInto::Pi { .. } => continue,
973                _ => return Ok(ev),
974            }
975        }
976    }
977
978    /// Skip an element's body (advance past its matching EndElement).
979    /// The opening Start has already been consumed.
980    fn skip_body(&mut self) -> Result<(), SchemaCompileError> {
981        let mut depth = 1usize;
982        while depth > 0 {
983            match self.next_event()? {
984                EventInto::StartElement { .. } => depth += 1,
985                EventInto::EndElement   { .. } => depth -= 1,
986                EventInto::Eof => return Err(self.err("unexpected EOF inside element")),
987                _ => {}
988            }
989        }
990        Ok(())
991    }
992
993    /// Parse `<xs:assert>` / `<xs:assertion>` and consume through
994    /// the matching EndElement.  Returns `Some(Assertion)` when
995    /// `test=` is present; returns `None` for an empty / missing
996    /// test (silently ignored — the schema is still well-formed
997    /// without the constraint).
998    ///
999    /// Captures the current namespace scope so the test expression
1000    /// can resolve prefixed names at evaluation time, and reads
1001    /// `xpathDefaultNamespace` if set.  The body may contain only
1002    /// an optional `<xs:annotation>`; anything else is silently
1003    /// skipped today (assertion-attached annotations aren't modelled).
1004    fn parse_assertion_body(&mut self, attrs: &[Attr<'a>])
1005        -> Result<Option<super::schema::Assertion>, SchemaCompileError>
1006    {
1007        let test    = self.attr(attrs, "test").map(str::to_string);
1008        let default = self.attr(attrs, "xpathDefaultNamespace").map(str::to_string);
1009        // Snapshot the in-scope namespaces — XPath in `test` resolves
1010        // prefixes against these at evaluation time.
1011        let namespaces: Vec<(Option<String>, String)> = self.ns_stack.last()
1012            .map(|top| top.iter()
1013                .map(|(prefix, uri)| {
1014                    let p = if prefix.is_empty() { None } else { Some(prefix.clone()) };
1015                    (p, uri.clone())
1016                })
1017                .collect())
1018            .unwrap_or_default();
1019        self.skip_body()?;
1020        Ok(test.filter(|t| !t.is_empty()).map(|test| super::schema::Assertion {
1021            test,
1022            namespaces,
1023            xpath_default_namespace: default,
1024        }))
1025    }
1026
1027    /// XSD §3.13.2 — `<xs:annotation>` body must contain only
1028    /// `<xs:appinfo>` and `<xs:documentation>` children (in any
1029    /// order). Nested `<xs:annotation>` or any other XS-namespace
1030    /// child is a schema validity error. Foreign-namespace
1031    /// elements are allowed and skipped.
1032    fn parse_annotation_body(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
1033        // XSD §3.13.2 — `<xs:annotation>` takes only `id` plus
1034        // foreign-namespace attributes.  `check_known_attrs` already
1035        // permits foreign attrs.
1036        self.check_known_attrs(attrs, &["id"], "annotation")?;
1037        loop {
1038            match self.next_event()? {
1039                EventInto::StartElement { name } => {
1040                    let child_attrs = self.take_attrs()?;
1041                    self.push_ns_scope(&child_attrs);
1042                    let qn = self.qname_of_element(&name)?;
1043                    if qn.namespace.as_deref() == Some(XS) {
1044                        match qn.local.as_ref() {
1045                            "appinfo" => {
1046                                self.check_known_attrs(&child_attrs, &["source"], "appinfo")?;
1047                            }
1048                            "documentation" => {
1049                                self.check_known_attrs(&child_attrs,
1050                                    &["source", "xml:lang"], "documentation")?;
1051                                if let Some(lang) = self.attr(&child_attrs, "xml:lang") {
1052                                    if lang.trim().is_empty() {
1053                                        return Err(self.err(
1054                                            "<xs:documentation xml:lang=...>: language tag \
1055                                             must not be empty or whitespace-only",
1056                                        ));
1057                                    }
1058                                }
1059                            }
1060                            other => return Err(self.err(format!(
1061                                "<xs:{other}> is not allowed as a child of <xs:annotation>"
1062                            ))),
1063                        }
1064                    }
1065                    // appinfo / documentation bodies are mixed content
1066                    // (any well-formed XML); we don't peek inside.
1067                    self.skip_body()?;
1068                    self.pop_ns_scope();
1069                }
1070                EventInto::EndElement { .. } => return Ok(()),
1071                EventInto::Eof => return Err(self.err("unexpected EOF inside <xs:annotation>")),
1072                _ => {}
1073            }
1074        }
1075    }
1076
1077    // ── top-level walk ───────────────────────────────────────────────────
1078
1079    fn parse_schema(&mut self) -> Result<(), SchemaCompileError> {
1080        // First non-trivia event must be <xs:schema>.
1081        let ev = self.next_event()?;
1082        let attrs = match ev {
1083            EventInto::StartElement { name } => {
1084                let attrs = self.take_attrs()?;
1085                self.push_ns_scope(&attrs);
1086                let qn = self.qname_of_element(&name)?;
1087                if qn.namespace.as_deref() != Some(XS) || qn.local.as_ref() != "schema" {
1088                    return Err(self.err(format!(
1089                        "root element must be xs:schema, got {qn}"
1090                    )));
1091                }
1092                attrs
1093            }
1094            _ => return Err(self.err("XSD must start with <xs:schema>")),
1095        };
1096        // XSD §3.15.2 — the root element's defined attributes
1097        // (`targetNamespace`, `attributeFormDefault`, `blockDefault`,
1098        // `elementFormDefault`, `finalDefault`, `id`, `version`,
1099        // `xml:lang`, plus the XSD 1.1 `defaultAttributes` and the
1100        // version-control `vc:*` set) must be unqualified.  Reject
1101        // any XSD-namespace-qualified spelling (e.g. `xsd:targetNamespace`)
1102        // — they're not the same slot per the schema-for-schemas.
1103        self.check_known_attrs(&attrs, &[
1104            "id", "targetNamespace", "version", "attributeFormDefault",
1105            "elementFormDefault", "blockDefault", "finalDefault",
1106            "defaultAttributes",
1107        ], "schema")?;
1108        // Pull targetNamespace + form defaults from the root.  For
1109        // includes the namespace must match the including schema's;
1110        // for the first/root file it sets the builder's target
1111        // namespace.  Form defaults are per-file and scope only the
1112        // declarations parsed below.
1113        let mut this_target_ns: Option<Arc<str>> = None;
1114        for a in &attrs {
1115            match a.name() {
1116                "targetNamespace" => {
1117                    let v = a.value.as_ref();
1118                    if v.is_empty() {
1119                        return Err(self.err(
1120                            "<xs:schema targetNamespace=\"\">: empty namespace URI is not allowed"
1121                        ));
1122                    }
1123                    this_target_ns = Some(Arc::from(v));
1124                }
1125                "elementFormDefault" => {
1126                    self.element_form_default = Form::parse(a.value.as_ref()).ok_or_else(|| self.err(
1127                        format!("<xs:schema elementFormDefault={:?}>: must be \"qualified\" or \"unqualified\"", a.value)
1128                    ))?;
1129                }
1130                "attributeFormDefault" => {
1131                    self.attribute_form_default = Form::parse(a.value.as_ref()).ok_or_else(|| self.err(
1132                        format!("<xs:schema attributeFormDefault={:?}>: must be \"qualified\" or \"unqualified\"", a.value)
1133                    ))?;
1134                }
1135                "blockDefault" => {
1136                    self.block_default = parse_block_set(Some(a.value.as_ref()))?;
1137                }
1138                "finalDefault" => {
1139                    self.final_default = parse_block_set(Some(a.value.as_ref()))?;
1140                }
1141                _ => {}
1142            }
1143        }
1144        // XSD 1.1 § F.1: `vc:minVersion` lets a schema author declare
1145        // they're using 1.1 features.  Honoured only in `Auto` mode
1146        // (the explicit Xsd10 / Xsd11 settings override).  When seen
1147        // and we promote to Xsd11, downstream wildcard / construct
1148        // checks gate on the new value.
1149        if matches!(self.builder.options.version, SchemaVersion::Auto) {
1150            let min_version = attrs.iter().find(|a| a.name() == "vc:minVersion");
1151            if let Some(mv) = min_version {
1152                if mv.value.as_ref().trim() == "1.1" {
1153                    self.builder.effective_version = SchemaVersion::Xsd11;
1154                }
1155            }
1156        }
1157        match (&self.expected_target_ns, &this_target_ns) {
1158            (Some(expected), Some(found)) => {
1159                if expected != &found.as_ref() {
1160                    return Err(self.err(format!(
1161                        "included schema has targetNamespace={found:?}, expected {expected:?}"
1162                    )));
1163                }
1164                self.current_target_ns = Some(found.clone());
1165            }
1166            (Some(expected), None) => {
1167                if self.is_import_target {
1168                    // XSD §4.2.3 — an imported schema document must
1169                    // declare the namespace named on the import.  A
1170                    // file with no `targetNamespace` cannot satisfy
1171                    // an import of a non-empty namespace.
1172                    return Err(self.err(format!(
1173                        "imported schema has no targetNamespace, expected {expected:?}"
1174                    )));
1175                }
1176                // Chameleon include — no targetNamespace declared,
1177                // adopts the including schema's namespace.
1178                self.current_target_ns = Some(Arc::from(*expected));
1179            }
1180            (None, Some(found)) => {
1181                if self.builder.depth > 0 {
1182                    // An included/redefined schema with a target
1183                    // namespace cannot be loaded into a parent that
1184                    // has none: the parent's chameleon rule needs the
1185                    // child to be unqualified (XSD §4.2.1).
1186                    return Err(self.err(format!(
1187                        "included schema has targetNamespace={found:?}, \
1188                         but the including schema has no targetNamespace"
1189                    )));
1190                }
1191                self.current_target_ns = Some(found.clone());
1192                if self.builder.target_ns.is_none() {
1193                    self.builder.target_ns = Some(found.clone());
1194                }
1195            }
1196            (None, None) => {} // top-level no-namespace schema.
1197        }
1198
1199        // Walk children.
1200        loop {
1201            match self.next_event()? {
1202                EventInto::StartElement { name } => {
1203                    let attrs = self.take_attrs()?;
1204                    self.push_ns_scope(&attrs);
1205                    let qn = self.qname_of_element(&name)?;
1206                    let ns = qn.namespace.as_deref();
1207                    if ns != Some(XS) {
1208                        if ns.is_none() {
1209                            // A no-namespace element directly under
1210                            // <xs:schema> isn't a valid schema component
1211                            // (the schema-for-schemas admits no such
1212                            // wildcard) — almost always an XSD element
1213                            // written without its namespace prefix.
1214                            return Err(self.err(format!(
1215                                "element <{}> at the schema top level is not in \
1216                                 the XML Schema namespace (missing prefix?)",
1217                                qn.local)));
1218                        }
1219                        // Foreign-namespace element at top level — skip
1220                        // (tolerate extension/annotation elements that
1221                        // real-world schemas place here).
1222                        self.skip_body()?;
1223                        self.pop_ns_scope();
1224                        continue;
1225                    }
1226                    match qn.local.as_ref() {
1227                        "element"        => self.parse_top_element(&attrs)?,
1228                        "attribute"      => self.parse_top_attribute(&attrs)?,
1229                        "simpleType"     => self.parse_top_simple_type(&attrs)?,
1230                        "complexType"    => self.parse_top_complex_type(&attrs)?,
1231                        "attributeGroup" => self.parse_top_attribute_group(&attrs)?,
1232                        "group"          => self.parse_top_group(&attrs)?,
1233                        "notation"       => self.parse_top_notation(&attrs)?,
1234                        "import"         => self.handle_import(&attrs)?,
1235                        "include"        => self.handle_include(&attrs)?,
1236                        "redefine"       => self.handle_redefine(&attrs)?,
1237                        "override" => {
1238                            if !matches!(self.builder.effective_version, SchemaVersion::Xsd11) {
1239                                return Err(self.err(
1240                                    "<xs:override> is an XSD 1.1 directive — \
1241                                     set SchemaOptions::version to Xsd11, or to Auto \
1242                                     with vc:minVersion=\"1.1\" on <xs:schema>",
1243                                ));
1244                            }
1245                            self.handle_override(&attrs)?;
1246                        }
1247                        "annotation" => self.parse_annotation_body(&attrs)?,
1248                        other => return Err(self.err(format!(
1249                            "unexpected top-level element <xs:{other}>"
1250                        ))),
1251                    }
1252                    self.pop_ns_scope();
1253                }
1254                EventInto::EndElement { .. } => break,
1255                EventInto::Eof => return Err(self.err("unexpected EOF")),
1256                _ => {}
1257            }
1258        }
1259        Ok(())
1260    }
1261
1262    // ── import / include ─────────────────────────────────────────────────
1263
1264    fn handle_import(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
1265        self.check_known_attrs(attrs, &["id", "namespace", "schemaLocation"], "import")?;
1266        let imported_ns = self.attr(attrs, "namespace").map(|s| s.to_owned());
1267        // Record the import for later src-resolve enforcement.  Use the
1268        // Arc<str> form so refs can compare cheaply.
1269        self.builder.imported_namespaces.insert(
1270            imported_ns.as_deref().map(Arc::from),
1271        );
1272        // XSD §4.2.3 — `namespace=""` (empty URI) is not a valid
1273        // anyURI; the no-namespace target is expressed by omitting
1274        // the attribute entirely.
1275        if imported_ns.as_deref() == Some("") {
1276            return Err(self.err(
1277                "<xs:import namespace=\"\">: empty namespace URI is not allowed \
1278                 (omit the attribute to import the no-namespace components)"
1279            ));
1280        }
1281        // XSD §4.2.3 — the imported namespace must differ from the
1282        // schema's own targetNamespace; importing yourself is a
1283        // schema validity error.  This also covers `<xs:import>`
1284        // with no `namespace` attribute (which means "the absent
1285        // namespace") from a schema that also has no
1286        // targetNamespace — they're the same "absent" namespace,
1287        // so self-import.
1288        if imported_ns.as_deref() == self.current_target_ns.as_deref() {
1289            return Err(self.err(format!(
1290                "<xs:import namespace={:?}> matches this schema's own \
1291                 targetNamespace — use <xs:include> for same-namespace composition",
1292                imported_ns.as_deref().unwrap_or(""),
1293            )));
1294        }
1295        let location    = self.attr(attrs, "schemaLocation");
1296        self.parse_anno_only_body("import")?;
1297
1298        let Some(loc) = location else {
1299            // The spec allows hint-less import; the schema relies on
1300            // the consumer providing the imported namespace by other
1301            // means.  We treat this as "nothing to load."
1302            return Ok(());
1303        };
1304        // Re-entrant import back into the root's target namespace:
1305        // loading that file would parse the root's content again
1306        // while the outer parser is still walking it, double-
1307        // inserting every declaration.  The root schema is already
1308        // authoritative for its target namespace, so skip the load.
1309        if let (Some(imp), Some(root)) = (
1310            imported_ns.as_deref(), self.builder.target_ns.as_deref(),
1311        ) {
1312            if imp == root {
1313                // NB: don't mark the location as loaded — a later
1314                // import of the same file for a different namespace
1315                // is a legitimate (if unusual) shape and must still
1316                // be validated against the file's targetNamespace.
1317                return Ok(());
1318            }
1319        }
1320        self.load_schema_via_resolver(loc, imported_ns.as_deref(), /*is_import=*/true)
1321    }
1322
1323    fn handle_include(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
1324        let location = self.attr(attrs, "schemaLocation").ok_or_else(||
1325            self.err("<xs:include> missing schemaLocation")
1326        )?.to_owned();
1327        self.parse_anno_only_body("include")?;
1328        // Includes inherit the including schema's targetNamespace.
1329        let expected_ns = self.current_target_ns.as_deref().map(str::to_owned);
1330        self.load_schema_via_resolver(&location, expected_ns.as_deref(), /*is_import=*/false)
1331    }
1332
1333    /// XSD §4.2.2 `<xs:redefine>` — load the referenced schema (like
1334    /// `<xs:include>`), then process the redefine body to override any
1335    /// same-named simpleType / complexType / group / attributeGroup.
1336    ///
1337    /// Loading runs before the body so the redefinitions, which insert
1338    /// into the same builder maps, win over the original definitions
1339    /// via `HashMap::insert` semantics.  References to the redefined
1340    /// names elsewhere in the redefining schema pick up the new
1341    /// definitions.
1342    fn handle_redefine(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
1343        self.check_known_attrs(attrs, &["id", "schemaLocation"], "redefine")?;
1344        let location = self.attr(attrs, "schemaLocation").ok_or_else(||
1345            self.err("<xs:redefine> missing schemaLocation")
1346        )?.to_owned();
1347        let expected_ns = self.current_target_ns.as_deref().map(str::to_owned);
1348        self.load_schema_via_resolver(&location, expected_ns.as_deref(), /*is_import=*/false)?;
1349
1350        // Snapshot the originals from the loaded schema.  When a
1351        // redefining complex type declares `<xs:extension base="X">`
1352        // or `<xs:restriction base="X">` where X is the same name as
1353        // the type being redefined, the base must resolve to the
1354        // pre-redefinition version (held in the snapshot), not the
1355        // redefining version that's about to overwrite it.  Without
1356        // this fixup the extension-merge post-pass detects a
1357        // self-cycle.
1358        let snapshot: HashMap<QName, TypeRef> = self.builder.types.clone();
1359        // Per src-redefine, the redefining body can only target names
1360        // that already exist in the loaded original. These snapshot
1361        // sets let `check_redefined_target_exists` recognise stale
1362        // redefinitions before they corrupt the post-pass merging.
1363        let snapshot_groups: HashSet<QName> = self.builder.model_groups.keys().cloned().collect();
1364        let snapshot_attr_groups: HashSet<QName> = self.builder.attr_groups.keys().cloned().collect();
1365
1366        let prev_in_redefine = self.in_redefine;
1367        self.in_redefine = true;
1368        let result = (|| -> Result<(), SchemaCompileError> {
1369            loop {
1370                match self.next_event()? {
1371                    EventInto::StartElement { name } => {
1372                        let child_attrs = self.take_attrs()?;
1373                        self.push_ns_scope(&child_attrs);
1374                        let qn = self.qname_of_element(&name)?;
1375                        if qn.namespace.as_deref() == Some(XS) {
1376                            match qn.local.as_ref() {
1377                                "simpleType"     => {
1378                                    self.check_redefined_type_exists(&child_attrs, &snapshot)?;
1379                                    self.last_simple_restriction_base = None;
1380                                    self.parse_top_simple_type(&child_attrs)?;
1381                                    self.check_redefined_simple_base(&child_attrs)?;
1382                                }
1383                                "complexType"    => {
1384                                    self.check_redefined_type_exists(&child_attrs, &snapshot)?;
1385                                    self.parse_top_complex_type(&child_attrs)?;
1386                                    self.check_redefined_complex_base(&child_attrs)?;
1387                                    self.fixup_redefined_complex_base(&child_attrs, &snapshot);
1388                                }
1389                                "group"          => {
1390                                    self.check_redefined_set_exists(&child_attrs,
1391                                        &snapshot_groups, "group")?;
1392                                    let qn = self.attr(&child_attrs, "name").map(|n| QName {
1393                                        namespace: self.current_target_ns.clone(),
1394                                        local:     Arc::from(n),
1395                                    });
1396                                    let original_particle = qn.as_ref()
1397                                        .and_then(|n| self.builder.model_groups.get(n))
1398                                        .map(|g| g.particle.clone());
1399                                    self.parse_top_group(&child_attrs)?;
1400                                    if let Some(name) = qn {
1401                                        self.builder.redefined_groups.insert(name.clone());
1402                                        if let (Some(original), Some(new_group)) =
1403                                            (original_particle, self.builder.model_groups.get(&name))
1404                                        {
1405                                            self.builder.redefined_group_originals.push((
1406                                                name,
1407                                                new_group.particle.clone(),
1408                                                original,
1409                                            ));
1410                                        }
1411                                    }
1412                                }
1413                                "attributeGroup" => {
1414                                    self.check_redefined_set_exists(&child_attrs,
1415                                        &snapshot_attr_groups, "attributeGroup")?;
1416                                    let qn = self.attr(&child_attrs, "name").map(|n| QName {
1417                                        namespace: self.current_target_ns.clone(),
1418                                        local:     Arc::from(n),
1419                                    });
1420                                    let original = qn.as_ref()
1421                                        .and_then(|n| self.builder.attr_groups.get(n))
1422                                        .cloned();
1423                                    self.builder.redefining_ag_name = qn.clone();
1424                                    self.builder.redefining_ag_saw_self_ref = false;
1425                                    self.parse_top_attribute_group(&child_attrs)?;
1426                                    let saw_self_ref = self.builder.redefining_ag_saw_self_ref;
1427                                    self.builder.redefining_ag_name = None;
1428                                    self.builder.redefining_ag_saw_self_ref = false;
1429                                    if let (Some(name), Some(orig)) = (qn, original) {
1430                                        if let Some(new_ag) = self.builder.attr_groups.get(&name) {
1431                                            self.builder.redefined_attr_group_originals.push((
1432                                                name, new_ag.clone(), orig, saw_self_ref,
1433                                            ));
1434                                        }
1435                                    }
1436                                }
1437                                "annotation"     => self.parse_annotation_body(&child_attrs)?,
1438                                other => return Err(self.err(format!(
1439                                    "<xs:redefine> body: unexpected child <xs:{other}>"
1440                                ))),
1441                            }
1442                        } else {
1443                            // Foreign-namespace child — skip per spec.
1444                            self.skip_body()?;
1445                        }
1446                        self.pop_ns_scope();
1447                    }
1448                    EventInto::EndElement { .. } => break,
1449                    EventInto::Eof => return Err(self.err("unexpected EOF in <xs:redefine>")),
1450                    _ => {}
1451                }
1452            }
1453            Ok(())
1454        })();
1455        self.in_redefine = prev_in_redefine;
1456        result
1457    }
1458
1459    /// XSD 1.1 § 4.2.5 `<xs:override>`.  Like `<xs:redefine>` it
1460    /// loads a referenced schema document and reparses selected
1461    /// top-level components, except:
1462    ///
1463    /// 1. The child components **replace** the loaded ones outright
1464    ///    — there's no requirement that a redefining type derive
1465    ///    from itself, so the `redefine` self-reference fixup
1466    ///    doesn't apply.
1467    /// 2. The child set is broader — `xs:element`, `xs:attribute`,
1468    ///    and `xs:notation` are permitted in addition to the
1469    ///    redefine set (simpleType / complexType / group /
1470    ///    attributeGroup).
1471    /// 3. Components in the loaded schema that are NOT named in the
1472    ///    override are kept as-is.  Replacement is by qualified
1473    ///    name; parsing each child top-level decl with the existing
1474    ///    parsers inserts into the same `builder.*` maps and
1475    ///    naturally overwrites the loaded entry.
1476    fn handle_override(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
1477        let location = self.attr(attrs, "schemaLocation").ok_or_else(||
1478            self.err("<xs:override> missing schemaLocation")
1479        )?.to_owned();
1480        let expected_ns = self.current_target_ns.as_deref().map(str::to_owned);
1481        self.load_schema_via_resolver(&location, expected_ns.as_deref(), /*is_import=*/false)?;
1482
1483        loop {
1484            match self.next_event()? {
1485                EventInto::StartElement { name } => {
1486                    let child_attrs = self.take_attrs()?;
1487                    self.push_ns_scope(&child_attrs);
1488                    let qn = self.qname_of_element(&name)?;
1489                    if qn.namespace.as_deref() == Some(XS) {
1490                        match qn.local.as_ref() {
1491                            "simpleType"     => self.parse_top_simple_type(&child_attrs)?,
1492                            "complexType"    => self.parse_top_complex_type(&child_attrs)?,
1493                            "group"          => self.parse_top_group(&child_attrs)?,
1494                            "attributeGroup" => self.parse_top_attribute_group(&child_attrs)?,
1495                            "element"        => self.parse_top_element(&child_attrs)?,
1496                            "attribute"      => self.parse_top_attribute(&child_attrs)?,
1497                            "notation"       => self.parse_top_notation(&child_attrs)?,
1498                            "annotation"     => self.parse_annotation_body(&child_attrs)?,
1499                            other => return Err(self.err(format!(
1500                                "<xs:override> body: unexpected child <xs:{other}>"
1501                            ))),
1502                        }
1503                    } else {
1504                        // Foreign-namespace child — skip per spec.
1505                        self.skip_body()?;
1506                    }
1507                    self.pop_ns_scope();
1508                }
1509                EventInto::EndElement { .. } => break,
1510                EventInto::Eof => return Err(self.err("unexpected EOF in <xs:override>")),
1511                _ => {}
1512            }
1513        }
1514        Ok(())
1515    }
1516
1517    /// After `parse_top_complex_type` inserts a redefining type, if
1518    /// its derivation base names itself, rebuild the type with the
1519    /// base resolved to the snapshotted original Arc.  No-op when
1520    /// the base names a different type (regular derivation chain) or
1521    /// when the redefined name wasn't in the snapshot.
1522    /// XSD §4.2.2 src-redefine — a redefining schema component must
1523    /// target a name already declared in the loaded original.
1524    /// Redefining a name that doesn't exist there is a hard error.
1525    fn check_redefined_type_exists(
1526        &self,
1527        child_attrs: &[Attr<'a>],
1528        snapshot:    &HashMap<QName, TypeRef>,
1529    ) -> Result<(), SchemaCompileError> {
1530        let Some(name) = self.attr(child_attrs, "name") else { return Ok(()); };
1531        let own_qn = QName {
1532            namespace: self.current_target_ns.clone(),
1533            local:     Arc::from(name),
1534        };
1535        if !snapshot.contains_key(&own_qn) {
1536            return Err(self.err(format!(
1537                "<xs:redefine>: cannot redefine type {:?} — the loaded original \
1538                 doesn't declare a type with this name (src-redefine)",
1539                own_qn.local,
1540            )));
1541        }
1542        Ok(())
1543    }
1544
1545    fn check_redefined_set_exists(
1546        &self,
1547        child_attrs: &[Attr<'a>],
1548        snapshot:    &HashSet<QName>,
1549        kind:        &str,
1550    ) -> Result<(), SchemaCompileError> {
1551        let Some(name) = self.attr(child_attrs, "name") else { return Ok(()); };
1552        let own_qn = QName {
1553            namespace: self.current_target_ns.clone(),
1554            local:     Arc::from(name),
1555        };
1556        if !snapshot.contains(&own_qn) {
1557            return Err(self.err(format!(
1558                "<xs:redefine>: cannot redefine {kind} {:?} — the loaded original \
1559                 doesn't declare a {kind} with this name (src-redefine)",
1560                own_qn.local,
1561            )));
1562        }
1563        Ok(())
1564    }
1565
1566    /// XSD §4.2.2 src-redefine — inside `<xs:redefine>`, a redefining
1567    /// `<xs:simpleType name="X">` must restrict the original `X`
1568    /// (transitively); its `<xs:restriction base=...>` is required and
1569    /// must resolve to `X` itself.
1570    fn check_redefined_simple_base(
1571        &mut self,
1572        child_attrs: &[Attr<'a>],
1573    ) -> Result<(), SchemaCompileError> {
1574        let Some(name) = self.attr(child_attrs, "name") else { return Ok(()); };
1575        let own_qn = QName {
1576            namespace: self.current_target_ns.clone(),
1577            local:     Arc::from(name),
1578        };
1579        let bad = match &self.last_simple_restriction_base {
1580            // No named base (e.g. inline anonymous base via nested
1581            // <xs:simpleType>) is not a self-restriction.
1582            None => true,
1583            Some(base) => base != &own_qn,
1584        };
1585        if bad {
1586            return Err(self.err(format!(
1587                "<xs:redefine><xs:simpleType name={:?}>: the redefining body must \
1588                 restrict {0:?} itself (its `<xs:restriction base=>` must resolve \
1589                 to {0:?}); a different base is not a self-restriction per \
1590                 src-redefine",
1591                own_qn.local,
1592            )));
1593        }
1594        Ok(())
1595    }
1596
1597    /// XSD §4.2.2 src-redefine — inside `<xs:redefine>`, a redefining
1598    /// `<xs:complexType name="X">` must derive from the original `X`
1599    /// via `<xs:restriction base="X">` or `<xs:extension base="X">`.
1600    fn check_redefined_complex_base(
1601        &mut self,
1602        child_attrs: &[Attr<'a>],
1603    ) -> Result<(), SchemaCompileError> {
1604        let Some(name) = self.attr(child_attrs, "name") else { return Ok(()); };
1605        let own_qn = QName {
1606            namespace: self.current_target_ns.clone(),
1607            local:     Arc::from(name),
1608        };
1609        let Some(TypeRef::Complex(redef)) = self.builder.types.get(&own_qn).cloned() else { return Ok(()) };
1610        let Some(d) = redef.derivation.as_ref() else {
1611            return Err(self.err(format!(
1612                "<xs:redefine><xs:complexType name={:?}>: the redefining body must \
1613                 derive (via <xs:restriction> or <xs:extension>) from the original \
1614                 {0:?}",
1615                own_qn.local,
1616            )));
1617        };
1618        let base_qn = resolve_typeref_to_qname(&d.base);
1619        if base_qn.as_ref() != Some(&own_qn) {
1620            return Err(self.err(format!(
1621                "<xs:redefine><xs:complexType name={:?}>: the redefining body's \
1622                 derivation base must be {0:?} itself (per src-redefine), not a \
1623                 different type",
1624                own_qn.local,
1625            )));
1626        }
1627        Ok(())
1628    }
1629
1630    fn fixup_redefined_complex_base(
1631        &mut self,
1632        child_attrs: &[Attr<'a>],
1633        snapshot:    &HashMap<QName, TypeRef>,
1634    ) {
1635        let Some(name) = self.attr(child_attrs, "name") else { return };
1636        let own_qn = QName {
1637            namespace: self.current_target_ns.clone(),
1638            local:     Arc::from(name),
1639        };
1640        let Some(TypeRef::Complex(redef)) = self.builder.types.get(&own_qn).cloned() else { return };
1641        let Some(d) = redef.derivation.as_ref() else { return };
1642        let Some(base_qn) = resolve_typeref_to_qname(&d.base) else { return };
1643        if base_qn != own_qn { return; }
1644        let Some(original) = snapshot.get(&own_qn).cloned() else { return };
1645        let new_derivation = Derivation {
1646            method: d.method,
1647            base:   original,
1648        };
1649        let new_ct = ComplexType {
1650            name:          redef.name.clone(),
1651            derivation:    Some(new_derivation),
1652            content:       redef.content.clone(),
1653            matcher:       std::sync::OnceLock::new(),
1654            attributes:    redef.attributes.clone(),
1655            any_attribute: redef.any_attribute.clone(),
1656            abstract_:     redef.abstract_,
1657            block:         redef.block,
1658            final_:        redef.final_,
1659            pending_attribute_group_refs: redef.pending_attribute_group_refs.clone(),
1660            assertions: redef.assertions.clone(),
1661        };
1662        self.builder.types.insert(own_qn, TypeRef::Complex(Arc::new(new_ct)));
1663    }
1664
1665    /// Shared body for import/include — fetch via resolver, parse
1666    /// recursively into the same builder.  Cycles are silently skipped.
1667    fn load_schema_via_resolver(
1668        &mut self,
1669        location: &str,
1670        expected_ns: Option<&str>,
1671        is_import: bool,
1672    ) -> Result<(), SchemaCompileError> {
1673        if self.builder.loaded.contains(location) {
1674            return Ok(()); // cycle — already loaded.
1675        }
1676        // XSD §4.2.3 (import) / §4.2.1 (include) — `schemaLocation`
1677        // is a hint. `Ok(None)` from the resolver means "I don't
1678        // have a mapping for this URI" and is a soft skip per spec
1679        // (declarations only present in the unresolved doc just
1680        // aren't available; the calling schema still compiles).
1681        //
1682        // `Err(_)` is different: the resolver tried and was actively
1683        // refused — `FilesystemResolver` rejecting a path outside
1684        // its allowlist, or `NetworkResolver` rejecting an
1685        // unauthorised host.  Silently absorbing those hides the
1686        // mismatch between the schema's intent and the runtime
1687        // security policy: the caller asked for an include they
1688        // can't fetch, so the resulting schema is missing the
1689        // types or groups they relied on.  Bubble that up as a
1690        // compile error so the operator sees the misconfiguration.
1691        let bytes = match self.resolver.resolve(location, expected_ns) {
1692            Ok(Some(b)) => b,
1693            Ok(None) => {
1694                self.builder.loaded.insert(location.to_owned());
1695                return Ok(());
1696            }
1697            Err(e) => {
1698                self.builder.loaded.insert(location.to_owned());
1699                return Err(self.err(format!(
1700                    "<xs:{}>: failed to resolve schemaLocation {location:?}: {e}",
1701                    if is_import { "import" } else { "include" },
1702                )));
1703            }
1704        };
1705        let s = std::str::from_utf8(&bytes).map_err(|e| self.err(format!(
1706            "schema {location:?} is not valid UTF-8: {e}"
1707        )))?;
1708
1709        self.builder.loaded.insert(location.to_owned());
1710        self.builder.depth += 1;
1711        let result = parse_one_file(s, self.builder, self.resolver, expected_ns, is_import);
1712        self.builder.depth -= 1;
1713        result
1714    }
1715
1716    /// Look up the qualified name for an element name (which the
1717    /// XmlReader hands us as `prefix:local` text).
1718    fn qname_of_element(&self, name: &str) -> Result<QName, SchemaCompileError> {
1719        match name.split_once(':') {
1720            Some((prefix, local)) => {
1721                let uri = self.resolve_prefix(prefix).ok_or_else(||
1722                    self.err(format!("undeclared element prefix {prefix:?}"))
1723                )?;
1724                Ok(QName::new(Some(uri), local))
1725            }
1726            None => {
1727                // Unprefixed element: default namespace.
1728                let uri = self.resolve_prefix("").map(|s| s.to_owned());
1729                Ok(QName {
1730                    namespace: uri.map(Arc::from),
1731                    local:     Arc::from(name),
1732                })
1733            }
1734        }
1735    }
1736
1737    // ── attribute helpers ────────────────────────────────────────────────
1738
1739    fn attr<'r>(&self, attrs: &'r [Attr<'a>], name: &str) -> Option<&'r str> {
1740        attrs.iter().find(|a| a.name() == name).map(|a| a.value.as_ref())
1741    }
1742
1743    /// Reject unrecognised attributes on an XSD element. Attributes
1744    /// in foreign namespaces (a real prefix bound to a non-XSD URI)
1745    /// are tolerated — they are the spec's extension mechanism.
1746    /// Attributes in the XSD namespace, no-namespace, or with an
1747    /// `xml:`/`xmlns:`/`xmlns` form are subject to the allow-list.
1748    fn check_known_attrs(
1749        &self,
1750        attrs:   &[Attr<'a>],
1751        allowed: &[&str],
1752        owner:   &str,
1753    ) -> Result<(), SchemaCompileError> {
1754        for a in attrs {
1755            let name = a.name;
1756            // Namespace declarations are always allowed.
1757            if name == "xmlns" || name.starts_with("xmlns:") { continue; }
1758            // `xml:`-prefixed attributes (xml:lang, xml:base, etc.)
1759            // are the XML core namespace and always allowed.
1760            if name.starts_with("xml:") { continue; }
1761            if let Some((prefix, _local)) = name.split_once(':') {
1762                let resolved = self.resolve_prefix(prefix);
1763                // A real foreign-namespace prefix: tolerate (extension
1764                // attribute, XSD §3.x.2 allows annotations of foreign
1765                // attrs on any schema component).
1766                if matches!(resolved, Some(uri) if uri != XS) {
1767                    continue;
1768                }
1769                // Prefix bound to the XSD namespace itself — XSD
1770                // attribute names are unqualified per the schema-for-
1771                // schemas, so `xsd:type` is *not* the same slot as the
1772                // unprefixed `type` and is unrecognised.
1773                return Err(self.err(format!(
1774                    "<xs:{owner}>: attribute {name:?} is in the XSD namespace; \
1775                     XSD-defined attributes on schema elements must be unqualified"
1776                )));
1777            } else {
1778                if !allowed.contains(&name) {
1779                    return Err(self.err(format!(
1780                        "<xs:{owner}>: attribute {name:?} is not recognised \
1781                         (allowed: {allowed:?})"
1782                    )));
1783                }
1784            }
1785        }
1786        Ok(())
1787    }
1788
1789    // ── top-level element ────────────────────────────────────────────────
1790
1791    fn parse_top_element(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
1792        // XSD §3.3.2 — the attribute set of a top-level <xs:element>.
1793        self.check_known_attrs(attrs, &[
1794            "id", "name", "type", "default", "fixed", "nillable",
1795            "abstract", "substitutionGroup", "block", "final",
1796        ], "element")?;
1797        let name = self.attr(attrs, "name").ok_or_else(||
1798            self.err("<xs:element> missing name attribute")
1799        )?.to_owned();
1800        let qn = QName {
1801            namespace: self.current_target_ns.clone(),
1802            local:     Arc::from(name.as_str()),
1803        };
1804
1805        // Type + identity constraints — parse_inline_type walks the body.
1806        let (inline, identity) = self.parse_inline_type(attrs)?;
1807        let type_def = match self.attr(attrs, "type") {
1808            Some(t) => {
1809                // XSD §3.3.3 src-element.3 — an element decl may have
1810                // either `type=` OR a nested `<xs:simpleType>` /
1811                // `<xs:complexType>` child, never both.
1812                if inline.is_some() {
1813                    return Err(self.err(format!(
1814                        "<xs:element name={name:?}> has both a `type=` attribute and \
1815                         a nested type body; pick one (XSD §3.3.3 src-element.3)",
1816                    )));
1817                }
1818                let type_qn = self.parse_qname(t, false)?;
1819                self.type_ref_for(type_qn)
1820            }
1821            None => match inline {
1822                Some(t) => t,
1823                None => any_type_ref(),
1824            },
1825        };
1826
1827        let nillable = self.parse_xsd_bool(attrs, "nillable")?.unwrap_or(false);
1828        let default  = self.attr(attrs, "default").map(|s| s.to_owned());
1829        let fixed    = self.attr(attrs, "fixed").map(|s| s.to_owned());
1830        if default.is_some() && fixed.is_some() {
1831            return Err(self.err(
1832                "<xs:element> may have either default= or fixed=, not both",
1833            ));
1834        }
1835        // XSD §3.3.3 (cvc-elt-2.x): a `default`/`fixed` value must
1836        // be valid against the element's type's value space. We can
1837        // check this for built-ins / already-declared simple types
1838        // and reject when the element has a complex type whose
1839        // content isn't simple — `default`/`fixed` only apply to
1840        // simple-typed elements (or mixed complex types, which we
1841        // don't differentiate here).
1842        if let Some(v) = default.as_deref().or(fixed.as_deref()) {
1843            match &type_def {
1844                TypeRef::Simple(st) => {
1845                    // XSD §3.3.6 — an element typed xs:ID (or derived
1846                    // from it) cannot have a default or fixed: ID
1847                    // validity is per-instance and a fixed/default ID
1848                    // would be illegal the moment the element appeared
1849                    // twice.
1850                    if matches!(st.builtin, super::types::BuiltinType::Id) {
1851                        return Err(self.err(format!(
1852                            "<xs:element name={name:?}> of type xs:ID (or derived) cannot have a default or fixed value",
1853                        )));
1854                    }
1855                    if let Err(e) = st.validate(v) {
1856                        return Err(self.err(format!(
1857                            "<xs:element name={name:?}> default/fixed value {v:?} is not valid for its type: {}",
1858                            e.message,
1859                        )));
1860                    }
1861                }
1862                TypeRef::Complex(ct) => {
1863                    // XSD §3.3.6 — default/fixed allowed when the
1864                    // content type is mixed or simple. Element-only
1865                    // (Complex with mixed=false) and empty content
1866                    // can't have a value constraint.
1867                    let allowed = match &ct.content {
1868                        crate::xsd::schema::ContentModel::Simple(_) => true,
1869                        crate::xsd::schema::ContentModel::Complex { mixed, .. } => *mixed,
1870                        crate::xsd::schema::ContentModel::Empty => false,
1871                    };
1872                    if !allowed {
1873                        return Err(self.err(format!(
1874                            "<xs:element name={name:?}> with element-only or empty content cannot \
1875                             have a default or fixed value (XSD §3.3.6)",
1876                        )));
1877                    }
1878                }
1879            }
1880        }
1881        let abstract_ = self.parse_xsd_bool(attrs, "abstract")?.unwrap_or(false);
1882        let substitution_group = match self.attr(attrs, "substitutionGroup") {
1883            Some(s) => Some(self.parse_qname(s, false)?),
1884            None    => None,
1885        };
1886
1887        let decl = Arc::new(ElementDecl {
1888            name: qn.clone(),
1889            type_def,
1890            nillable,
1891            default,
1892            fixed,
1893            abstract_,
1894            substitution_group,
1895            // XSD §3.3.2 — `block` on <xs:element> accepts only
1896            // restriction | extension | substitution | #all.
1897            // The simple-type tokens `list` / `union` are NOT
1898            // allowed here.
1899            block:  match self.attr(attrs, "block") {
1900                Some(_) => parse_element_block_set(self.attr(attrs, "block"))?,
1901                None    => self.block_default
1902                            & (BlockSet::RESTRICTION
1903                               | BlockSet::EXTENSION
1904                               | BlockSet::SUBSTITUTION),
1905            },
1906            // XSD §3.3.2: `final` on <xs:element> accepts only
1907            // restriction|extension|#all; substitution is element's
1908            // block-only token.
1909            final_: match self.attr(attrs, "final") {
1910                Some(_) => parse_ct_derivation_set(self.attr(attrs, "final"), "final")?,
1911                None    => self.final_default
1912                            & (BlockSet::RESTRICTION | BlockSet::EXTENSION),
1913            },
1914            identity,
1915        });
1916        // Duplicate detection is per-file: include cycles are allowed
1917        // to re-process the same top-level decl across files (the
1918        // re-insertion is benign), but a single file declaring two
1919        // elements with the same name is a schema validity error.
1920        if !self.local_top_element_names.insert(qn.clone()) {
1921            return Err(self.err(format!(
1922                "duplicate top-level <xs:element name={:?}> in this schema document",
1923                qn.local,
1924            )));
1925        }
1926        self.builder.elements.insert(qn, decl);
1927        Ok(())
1928    }
1929
1930    /// Parse an inline `<xs:simpleType>` or `<xs:complexType>` child
1931    /// nested in an element/attribute decl, plus any
1932    /// `<xs:key>`/`<xs:keyref>`/`<xs:unique>` constraints.  Consumes
1933    /// events through the parent's matching EndElement.
1934    fn parse_inline_type(
1935        &mut self,
1936        _attrs: &[Attr<'a>],
1937    ) -> Result<(Option<TypeRef>, Vec<super::identity::IdentityConstraint>), SchemaCompileError> {
1938        let mut found_type: Option<TypeRef> = None;
1939        let mut constraints: Vec<super::identity::IdentityConstraint> = Vec::new();
1940        let mut seen_anno = false;
1941        let mut seen_other = false;
1942        loop {
1943            match self.next_event()? {
1944                EventInto::StartElement { name } => {
1945                    let child_attrs = self.take_attrs()?;
1946                    self.push_ns_scope(&child_attrs);
1947                    let qn = self.qname_of_element(&name)?;
1948                    let is_xs = qn.namespace.as_deref() == Some(XS);
1949                    if is_xs {
1950                        self.check_annotation_pos(
1951                            &qn.local, &mut seen_anno, &mut seen_other, "element",
1952                        )?;
1953                    }
1954                    match (is_xs, qn.local.as_ref()) {
1955                        (true, "simpleType") => {
1956                            let st = self.parse_simple_type_body(&child_attrs)?;
1957                            found_type = Some(TypeRef::Simple(Arc::new(st)));
1958                        }
1959                        (true, "complexType") => {
1960                            let ct = self.parse_complex_type_body(&child_attrs)?;
1961                            found_type = Some(TypeRef::Complex(Arc::new(ct)));
1962                        }
1963                        (true, "annotation") => self.parse_annotation_body(&child_attrs)?,
1964                        (true, kind @ ("key" | "keyref" | "unique")) => {
1965                            constraints.push(self.parse_identity_constraint(&child_attrs, kind)?);
1966                        }
1967                        (true, other) => return Err(self.err(format!(
1968                            "<xs:{other}> is not allowed as a child of <xs:element>"
1969                        ))),
1970                        _ => self.skip_body()?,
1971                    }
1972                    self.pop_ns_scope();
1973                }
1974                EventInto::EndElement { .. } => return Ok((found_type, constraints)),
1975                EventInto::Eof => return Err(self.err("unexpected EOF in element body")),
1976                _ => {}
1977            }
1978        }
1979    }
1980
1981    /// Parse one `<xs:key>` / `<xs:keyref>` / `<xs:unique>` element
1982    /// (the start tag and attributes have already been consumed; we
1983    /// walk through the matching EndElement).
1984    fn parse_identity_constraint(
1985        &mut self,
1986        attrs: &[Attr<'a>],
1987        kind_str: &str,
1988    ) -> Result<super::identity::IdentityConstraint, SchemaCompileError> {
1989        use super::identity::ConstraintKind;
1990        let kind = match kind_str {
1991            "key"     => ConstraintKind::Key,
1992            "unique"  => ConstraintKind::Unique,
1993            "keyref"  => ConstraintKind::KeyRef,
1994            _         => unreachable!(),
1995        };
1996        let name_str = self.attr(attrs, "name").ok_or_else(||
1997            self.err(format!("<xs:{kind_str}> missing name"))
1998        )?.to_owned();
1999        let name = QName {
2000            namespace: self.current_target_ns.clone(),
2001            local:     Arc::from(name_str.as_str()),
2002        };
2003        if !self.seen_ic_names.insert(name.clone()) {
2004            return Err(self.err(format!(
2005                "duplicate identity-constraint name {name_str:?} \
2006                 (unique/key/keyref share one namespace, XSD §3.11.1)"
2007            )));
2008        }
2009        let refer = if kind == ConstraintKind::KeyRef {
2010            let r = self.attr(attrs, "refer").ok_or_else(||
2011                self.err("<xs:keyref> missing refer attribute")
2012            )?;
2013            Some(self.parse_qname(r, false)?)
2014        } else {
2015            if self.attr(attrs, "refer").is_some() {
2016                return Err(self.err(format!(
2017                    "<xs:{kind_str} refer=...> is only valid on <xs:keyref>"
2018                )));
2019            }
2020            None
2021        };
2022
2023        // Snapshot prefix bindings from the current scope for XPath
2024        // resolution — XSD identity-constraint XPaths use the bindings
2025        // in scope at the constraint's own declaration.
2026        let prefix_lookup: Vec<(String, String)> = self.ns_stack.iter()
2027            .flat_map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())))
2028            .collect();
2029        let resolve_prefix = |p: &str| -> Option<String> {
2030            for (k, v) in prefix_lookup.iter().rev() {
2031                if k == p { return Some(v.clone()); }
2032            }
2033            None
2034        };
2035
2036        // Walk the body for <xs:selector> and one or more <xs:field>.
2037        let mut selector_xpath: Option<String> = None;
2038        let mut field_xpaths: Vec<String> = Vec::new();
2039        let mut seen_anno = false;
2040        let mut seen_other = false;
2041        loop {
2042            match self.next_event()? {
2043                EventInto::StartElement { name: child } => {
2044                    let cattrs = self.take_attrs()?;
2045                    self.push_ns_scope(&cattrs);
2046                    let qn = self.qname_of_element(&child)?;
2047                    if qn.namespace.as_deref() == Some(XS) {
2048                        self.check_annotation_pos(
2049                            &qn.local, &mut seen_anno, &mut seen_other, kind_str,
2050                        )?;
2051                        match qn.local.as_ref() {
2052                            "selector" => {
2053                                if selector_xpath.is_some() {
2054                                    return Err(self.err(format!(
2055                                        "<xs:{kind_str}> may have at most one <xs:selector>"
2056                                    )));
2057                                }
2058                                if !field_xpaths.is_empty() {
2059                                    return Err(self.err(format!(
2060                                        "<xs:{kind_str}> body: <xs:selector> must precede <xs:field>"
2061                                    )));
2062                                }
2063                                if self.attr(&cattrs, "name").is_some() {
2064                                    return Err(self.err(
2065                                        "<xs:selector> does not take a 'name' attribute",
2066                                    ));
2067                                }
2068                                let xp = self.attr(&cattrs, "xpath").ok_or_else(||
2069                                    self.err("<xs:selector> missing xpath")
2070                                )?.to_owned();
2071                                selector_xpath = Some(xp);
2072                                self.parse_anno_only_body("selector")?;
2073                                self.pop_ns_scope();
2074                                continue;
2075                            }
2076                            "field" => {
2077                                if self.attr(&cattrs, "name").is_some() {
2078                                    return Err(self.err(
2079                                        "<xs:field> does not take a 'name' attribute",
2080                                    ));
2081                                }
2082                                let xp = self.attr(&cattrs, "xpath").ok_or_else(||
2083                                    self.err("<xs:field> missing xpath")
2084                                )?.to_owned();
2085                                field_xpaths.push(xp);
2086                                self.parse_anno_only_body("field")?;
2087                                self.pop_ns_scope();
2088                                continue;
2089                            }
2090                            "annotation" => {}
2091                            other => return Err(self.err(format!(
2092                                "<xs:{other}> is not allowed as a child of <xs:{kind_str}>"
2093                            ))),
2094                        }
2095                    }
2096                    self.skip_body()?;
2097                    self.pop_ns_scope();
2098                }
2099                EventInto::EndElement { .. } => break,
2100                EventInto::Eof => return Err(self.err(format!(
2101                    "unexpected EOF in <xs:{kind_str}>"
2102                ))),
2103                _ => {}
2104            }
2105        }
2106
2107        let selector_xpath = selector_xpath.ok_or_else(||
2108            self.err(format!("<xs:{kind_str} name={:?}> has no <xs:selector>", name.local))
2109        )?;
2110        if field_xpaths.is_empty() {
2111            return Err(self.err(format!(
2112                "<xs:{kind_str} name={:?}> has no <xs:field>", name.local
2113            )));
2114        }
2115
2116        let selector = super::identity::parse_selector(&selector_xpath, &resolve_prefix)
2117            .map_err(|e| self.err(format!("<xs:selector xpath={selector_xpath:?}>: {e}")))?;
2118        let fields: Vec<_> = field_xpaths.iter()
2119            .map(|fp| super::identity::parse_field(fp, &resolve_prefix)
2120                .map_err(|e| self.err(format!("<xs:field xpath={fp:?}>: {e}"))))
2121            .collect::<Result<_, _>>()?;
2122
2123        Ok(super::identity::IdentityConstraint {
2124            name, kind, selector, fields, refer,
2125        })
2126    }
2127
2128    // ── top-level attribute ──────────────────────────────────────────────
2129
2130    fn parse_top_attribute(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
2131        // XSD §3.2.2 — the attribute set of top-level <xs:attribute>.
2132        self.check_known_attrs(attrs, &[
2133            "id", "name", "type", "default", "fixed",
2134        ], "attribute")?;
2135        let name = self.attr(attrs, "name").ok_or_else(||
2136            self.err("<xs:attribute> missing name attribute")
2137        )?.to_owned();
2138        // XSD §3.2.3 — the name "xmlns" is reserved (it is the
2139        // namespace-binding pseudo-attribute, not an XSD-declared
2140        // attribute) and must not be used here.
2141        if name == "xmlns" {
2142            return Err(self.err(
2143                "<xs:attribute name=\"xmlns\"> is reserved by the Namespaces in XML spec",
2144            ));
2145        }
2146        // `form=`, `use=`, and `ref=` are only valid on local
2147        // attribute declarations (XSD §3.2.2).
2148        if self.attr(attrs, "form").is_some() {
2149            return Err(self.err(
2150                "<xs:attribute form=...> is only valid on local attribute declarations"
2151            ));
2152        }
2153        if self.attr(attrs, "use").is_some() {
2154            return Err(self.err(
2155                "<xs:attribute use=...> is only valid on local attribute declarations"
2156            ));
2157        }
2158        if self.attr(attrs, "ref").is_some() {
2159            return Err(self.err(
2160                "<xs:attribute ref=...> is only valid on local attribute declarations"
2161            ));
2162        }
2163        // `default=` and `fixed=` are mutually exclusive (XSD §3.2.2).
2164        if self.attr(attrs, "default").is_some() && self.attr(attrs, "fixed").is_some() {
2165            return Err(self.err(
2166                "<xs:attribute> may have either default= or fixed=, not both"
2167            ));
2168        }
2169        let qn = QName {
2170            namespace: self.current_target_ns.clone(),
2171            local:     Arc::from(name.as_str()),
2172        };
2173        check_xsi_attribute_name(&qn).map_err(|m| self.err(m))?;
2174
2175        // Resolve the simple type — `type=` ref or inline simpleType.
2176        let inline_type = self.parse_inline_simple_type(attrs)?;
2177        if self.attr(attrs, "type").is_some() && inline_type.is_some() {
2178            return Err(self.err(
2179                "<xs:attribute> may have either type= or an inline <xs:simpleType>, not both"
2180            ));
2181        }
2182        let st = match self.attr(attrs, "type") {
2183            Some(t) => {
2184                let type_qn = self.parse_qname(t, false)?;
2185                self.simple_type_for(type_qn)
2186            }
2187            None => match inline_type {
2188                Some(t) => Arc::new(t),
2189                None => Arc::new(SimpleType::of_builtin(BuiltinType::String)),
2190            },
2191        };
2192
2193        let decl = Arc::new(AttributeDecl {
2194            name:    qn.clone(),
2195            type_def: st,
2196            default: self.attr(attrs, "default").map(|s| s.to_owned()),
2197            fixed:   self.attr(attrs, "fixed").map(|s| s.to_owned()),
2198            inheritable: self.parse_inheritable(attrs)?,
2199        });
2200        if self.builder.attributes.contains_key(&qn) {
2201            return Err(self.err(format!(
2202                "duplicate top-level <xs:attribute name={:?}> in target namespace",
2203                qn.local,
2204            )));
2205        }
2206        self.builder.attributes.insert(qn, decl);
2207        Ok(())
2208    }
2209
2210    /// Walk attribute-decl body for an inline `<xs:simpleType>`. The
2211    /// only XSD children allowed inside an `<xs:attribute>` are
2212    /// `<xs:annotation>` and `<xs:simpleType>` (XSD §3.2.2); reject
2213    /// anything else so things like `<xs:unique>` inside an attribute
2214    /// surface as the schema validity errors they are. Annotation
2215    /// must precede simpleType and only appears once.
2216    fn parse_inline_simple_type(&mut self, _attrs: &[Attr<'a>])
2217        -> Result<Option<SimpleType>, SchemaCompileError>
2218    {
2219        let mut found: Option<SimpleType> = None;
2220        let mut seen_anno = false;
2221        let mut seen_other = false;
2222        loop {
2223            match self.next_event()? {
2224                EventInto::StartElement { name } => {
2225                    let child_attrs = self.take_attrs()?;
2226                    self.push_ns_scope(&child_attrs);
2227                    let qn = self.qname_of_element(&name)?;
2228                    if qn.namespace.as_deref() == Some(XS) {
2229                        self.check_annotation_pos(
2230                            &qn.local, &mut seen_anno, &mut seen_other, "attribute",
2231                        )?;
2232                        match qn.local.as_ref() {
2233                            "simpleType" => {
2234                                if found.is_some() {
2235                                    return Err(self.err(
2236                                        "<xs:attribute> may have at most one inline <xs:simpleType>",
2237                                    ));
2238                                }
2239                                found = Some(self.parse_simple_type_body(&child_attrs)?);
2240                            }
2241                            "annotation" => self.parse_annotation_body(&child_attrs)?,
2242                            other => return Err(self.err(format!(
2243                                "<xs:{other}> is not allowed as a child of <xs:attribute>"
2244                            ))),
2245                        }
2246                    } else {
2247                        self.skip_body()?;
2248                    }
2249                    self.pop_ns_scope();
2250                }
2251                EventInto::EndElement { .. } => return Ok(found),
2252                EventInto::Eof => return Err(self.err("unexpected EOF")),
2253                _ => {}
2254            }
2255        }
2256    }
2257
2258    // ── top-level simpleType ─────────────────────────────────────────────
2259
2260    fn parse_top_simple_type(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
2261        // XSD §3.14.1 — a top-level <xs:simpleType> requires `name`.
2262        if self.attr(attrs, "name").is_none() {
2263            return Err(self.err(
2264                "top-level <xs:simpleType> requires a 'name' attribute",
2265            ));
2266        }
2267        // Track the in-flight name so the restriction body can detect
2268        // a self-reference (`<xs:simpleType name="X"><xs:restriction
2269        // base="X">`) and reject it — outside <xs:redefine> this is a
2270        // direct circular definition, not a derivation.
2271        let prev_in_flight = self.simple_type_in_flight.take();
2272        let name_qn = self.attr(attrs, "name").map(|n| QName {
2273            namespace: self.current_target_ns.clone(),
2274            local:     Arc::from(n),
2275        });
2276        self.simple_type_in_flight = name_qn.clone();
2277        let st = self.parse_simple_type_body_inner(attrs, /*allow_name=*/true);
2278        self.simple_type_in_flight = prev_in_flight;
2279        let st = st?;
2280        if let Some(name) = &st.name {
2281            let qn = QName {
2282                namespace: self.current_target_ns.clone(),
2283                local:     name.clone(),
2284            };
2285            if !self.in_redefine && self.builder.types.contains_key(&qn) {
2286                return Err(self.err(format!(
2287                    "duplicate top-level type definition {:?} \
2288                     (a type with this name is already declared)",
2289                    qn.local,
2290                )));
2291            }
2292            self.builder.types.insert(qn, TypeRef::Simple(Arc::new(st)));
2293        }
2294        Ok(())
2295    }
2296
2297    /// Parse the body of a `<xs:simpleType>` (the opening Start has
2298    /// already been consumed; we walk through the matching EndElement).
2299    /// Used at inline call sites (inside element/attribute/list/union/
2300    /// restriction); XSD §3.14.2 forbids `name` on those, so we reject
2301    /// it here.
2302    fn parse_simple_type_body(&mut self, attrs: &[Attr<'a>])
2303        -> Result<SimpleType, SchemaCompileError>
2304    {
2305        self.parse_simple_type_body_inner(attrs, /*allow_name=*/false)
2306    }
2307
2308    /// XSD §3.14.2 — body is `(annotation?, (restriction | list | union))`:
2309    /// exactly one of the three is required, and any other XS child or
2310    /// a second derivation is a schema validity error.
2311    fn parse_simple_type_body_inner(&mut self, attrs: &[Attr<'a>], allow_name: bool)
2312        -> Result<SimpleType, SchemaCompileError>
2313    {
2314        if !allow_name && self.attr(attrs, "name").is_some() {
2315            return Err(self.err(
2316                "inline <xs:simpleType> must not have a 'name' attribute (XSD §3.14.2)",
2317            ));
2318        }
2319        let name: Option<Arc<str>> = self.attr(attrs, "name").map(Arc::from);
2320
2321        // XSD §3.14.6 — top-level simpleType honours `final`; the
2322        // schema-level `finalDefault` supplies the value when omitted.
2323        // Anonymous inline simpleTypes can't be referenced, so any
2324        // value here is effectively inert — we still parse it for
2325        // completeness.
2326        let final_ = match self.attr(attrs, "final") {
2327            Some(_) => parse_block_set(self.attr(attrs, "final"))?,
2328            None    => self.final_default,
2329        };
2330
2331        let mut builtin = BuiltinType::String;
2332        let mut facets  = FacetSet::default();
2333        let mut whitespace = WhitespaceMode::Preserve;
2334        let mut variety: super::types::Variety = super::types::Variety::Atomic;
2335        let mut assertions: Vec<super::schema::Assertion> = Vec::new();
2336        let mut seen_anno = false;
2337        let mut seen_other = false;
2338        let mut saw_derivation = false;
2339
2340        loop {
2341            match self.next_event()? {
2342                EventInto::StartElement { name: child_name } => {
2343                    let child_attrs = self.take_attrs()?;
2344                    self.push_ns_scope(&child_attrs);
2345                    let qn = self.qname_of_element(&child_name)?;
2346                    if qn.namespace.as_deref() == Some(XS) {
2347                        self.check_annotation_pos(
2348                            &qn.local, &mut seen_anno, &mut seen_other, "simpleType",
2349                        )?;
2350                        let local = qn.local.as_ref();
2351                        if matches!(local, "restriction" | "list" | "union")
2352                            && saw_derivation
2353                        {
2354                            return Err(self.err(
2355                                "<xs:simpleType> must have exactly one of \
2356                                 <xs:restriction>, <xs:list>, or <xs:union>",
2357                            ));
2358                        }
2359                        match local {
2360                            "restriction" => {
2361                                saw_derivation = true;
2362                                let (b, f, ws, v, a) = self.parse_simple_restriction(&child_attrs)?;
2363                                builtin = b; facets = f; whitespace = ws; variety = v;
2364                                assertions = a;
2365                            }
2366                            "list" => {
2367                                saw_derivation = true;
2368                                variety = super::types::Variety::List {
2369                                    item_type: self.parse_list_body(&child_attrs)?,
2370                                };
2371                                // List value-space is the string form;
2372                                // facets layered above (via a wrapping
2373                                // restriction) operate on item count
2374                                // per spec.
2375                                builtin = BuiltinType::String;
2376                                whitespace = WhitespaceMode::Collapse;
2377                            }
2378                            "union" => {
2379                                saw_derivation = true;
2380                                variety = super::types::Variety::Union {
2381                                    members: self.parse_union_body(&child_attrs)?,
2382                                };
2383                                builtin = BuiltinType::String;
2384                                whitespace = WhitespaceMode::Collapse;
2385                            }
2386                            "annotation" => self.parse_annotation_body(&child_attrs)?,
2387                            other => return Err(self.err(format!(
2388                                "<xs:{other}> is not allowed as a child of <xs:simpleType>"
2389                            ))),
2390                        }
2391                    } else {
2392                        self.skip_body()?;
2393                    }
2394                    self.pop_ns_scope();
2395                }
2396                EventInto::EndElement { .. } => break,
2397                EventInto::Eof => return Err(self.err("unexpected EOF in simpleType")),
2398                _ => {}
2399            }
2400        }
2401        if !saw_derivation {
2402            return Err(self.err(
2403                "<xs:simpleType> requires one of <xs:restriction>, <xs:list>, or <xs:union>",
2404            ));
2405        }
2406
2407        Ok(SimpleType {
2408            name, builtin, facets, whitespace, variety, final_,
2409            assertions,
2410        })
2411    }
2412
2413    /// Parse the body of `<xs:list itemType="..."/>` (or with a nested
2414    /// `<xs:simpleType>` child providing an anonymous item type).
2415    /// Consumes through the matching EndElement.
2416    fn parse_list_body(&mut self, attrs: &[Attr<'a>])
2417        -> Result<Arc<SimpleType>, SchemaCompileError>
2418    {
2419        // XSD §3.15.2 — body of <xs:list> is (annotation?, simpleType?).
2420        // `itemType` and the inline simpleType are mutually exclusive,
2421        // and at least one of them is required.
2422        let item_type_attr = self.attr(attrs, "itemType").map(|s| s.to_string());
2423        let mut nested: Option<SimpleType> = None;
2424        let mut seen_anno = false;
2425        let mut seen_other = false;
2426        loop {
2427            match self.next_event()? {
2428                EventInto::StartElement { name: child_name } => {
2429                    let child_attrs = self.take_attrs()?;
2430                    self.push_ns_scope(&child_attrs);
2431                    let qn = self.qname_of_element(&child_name)?;
2432                    if qn.namespace.as_deref() == Some(XS) {
2433                        self.check_annotation_pos(
2434                            &qn.local, &mut seen_anno, &mut seen_other, "list",
2435                        )?;
2436                        match qn.local.as_ref() {
2437                            "simpleType" => {
2438                                if nested.is_some() {
2439                                    return Err(self.err(
2440                                        "<xs:list> may have at most one inline <xs:simpleType>",
2441                                    ));
2442                                }
2443                                if item_type_attr.is_some() {
2444                                    return Err(self.err(
2445                                        "<xs:list> cannot have both itemType= and an inline <xs:simpleType>",
2446                                    ));
2447                                }
2448                                nested = Some(self.parse_simple_type_body(&child_attrs)?);
2449                            }
2450                            "annotation" => self.parse_annotation_body(&child_attrs)?,
2451                            other => return Err(self.err(format!(
2452                                "<xs:{other}> is not allowed as a child of <xs:list>"
2453                            ))),
2454                        }
2455                    } else {
2456                        self.skip_body()?;
2457                    }
2458                    self.pop_ns_scope();
2459                }
2460                EventInto::EndElement { .. } => break,
2461                EventInto::Eof => return Err(self.err("unexpected EOF in <xs:list>")),
2462                _ => {}
2463            }
2464        }
2465        if let Some(t) = nested {
2466            return Ok(Arc::new(t));
2467        }
2468        let item = item_type_attr.ok_or_else(||
2469            self.err("<xs:list> needs itemType= or a nested <xs:simpleType>"))?;
2470        let qn = self.parse_qname(&item, false)?;
2471        // XSD §3.15.6 (itemType-valid): the itemType must be a simple
2472        // type, never a complex type, and its variety must be atomic
2473        // or union — list-of-list isn't permitted.
2474        if let Some(TypeRef::Simple(st)) = self.builder.types.get(&qn) {
2475            if matches!(st.variety, super::types::Variety::List { .. }) {
2476                return Err(self.err(format!(
2477                    "<xs:list itemType={item:?}>: itemType must be atomic or union, not a list"
2478                )));
2479            }
2480            if st.final_.contains(super::schema::BlockSet::LIST) {
2481                return Err(self.err(format!(
2482                    "<xs:list itemType={item:?}>: base simple type's `final` disallows list derivation"
2483                )));
2484            }
2485        }
2486        if let Some(TypeRef::Complex(_)) = self.builder.types.get(&qn) {
2487            return Err(self.err(format!(
2488                "<xs:list itemType={item:?}>: itemType must be a simple type"
2489            )));
2490        }
2491        Ok(self.simple_type_for(qn))
2492    }
2493
2494    /// Parse the body of `<xs:union memberTypes="QName QName ..."/>` plus
2495    /// any nested `<xs:simpleType>` children (the anonymous-member form).
2496    /// Members appear in the order: memberTypes first (left-to-right),
2497    /// then nested simpleTypes — matching XSD §3.16.
2498    fn parse_union_body(&mut self, attrs: &[Attr<'a>])
2499        -> Result<Vec<Arc<SimpleType>>, SchemaCompileError>
2500    {
2501        let mut members: Vec<Arc<SimpleType>> = Vec::new();
2502        if let Some(list) = self.attr(attrs, "memberTypes") {
2503            for token in list.split_whitespace() {
2504                let qn = self.parse_qname(token, false)?;
2505                // XSD §3.16.6 — each memberType must be a simple type.
2506                if let Some(TypeRef::Complex(_)) = self.builder.types.get(&qn) {
2507                    return Err(self.err(format!(
2508                        "<xs:union memberTypes={list:?}>: {token:?} is not a simple type"
2509                    )));
2510                }
2511                if let Some(TypeRef::Simple(st)) = self.builder.types.get(&qn) {
2512                    if st.final_.contains(super::schema::BlockSet::UNION) {
2513                        return Err(self.err(format!(
2514                            "<xs:union memberTypes={list:?}>: {token:?}'s `final` \
2515                             disallows union derivation"
2516                        )));
2517                    }
2518                }
2519                if qn.namespace.as_deref() == Some(XS)
2520                    && qn.local.as_ref() != "anySimpleType"
2521                    && BuiltinType::from_name(&qn.local).is_none()
2522                {
2523                    return Err(self.err(format!(
2524                        "<xs:union memberTypes={list:?}>: {token:?} is not a built-in XSD type"
2525                    )));
2526                }
2527                members.push(self.simple_type_for(qn));
2528            }
2529        }
2530        // XSD §3.16.2 — body of <xs:union> is (annotation?, simpleType*).
2531        let mut seen_anno = false;
2532        let mut seen_other = false;
2533        loop {
2534            match self.next_event()? {
2535                EventInto::StartElement { name: child_name } => {
2536                    let child_attrs = self.take_attrs()?;
2537                    self.push_ns_scope(&child_attrs);
2538                    let qn = self.qname_of_element(&child_name)?;
2539                    if qn.namespace.as_deref() == Some(XS) {
2540                        self.check_annotation_pos(
2541                            &qn.local, &mut seen_anno, &mut seen_other, "union",
2542                        )?;
2543                        match qn.local.as_ref() {
2544                            "simpleType" => {
2545                                let st = self.parse_simple_type_body(&child_attrs)?;
2546                                members.push(Arc::new(st));
2547                            }
2548                            "annotation" => self.parse_annotation_body(&child_attrs)?,
2549                            other => return Err(self.err(format!(
2550                                "<xs:{other}> is not allowed as a child of <xs:union>"
2551                            ))),
2552                        }
2553                    } else {
2554                        self.skip_body()?;
2555                    }
2556                    self.pop_ns_scope();
2557                }
2558                EventInto::EndElement { .. } => break,
2559                EventInto::Eof => return Err(self.err("unexpected EOF in <xs:union>")),
2560                _ => {}
2561            }
2562        }
2563        if members.is_empty() {
2564            return Err(self.err(
2565                "<xs:union> needs memberTypes= or at least one nested <xs:simpleType>"
2566            ));
2567        }
2568        Ok(members)
2569    }
2570
2571    /// Parse `<xs:restriction base="…">` body, returning the base
2572    /// built-in plus any facets.
2573    fn parse_simple_restriction(&mut self, attrs: &[Attr<'a>])
2574        -> Result<(BuiltinType, FacetSet, WhitespaceMode, super::types::Variety,
2575                   Vec<super::schema::Assertion>), SchemaCompileError>
2576    {
2577        // Resolve the base's value-space.  Built-in `xs:*` bases map
2578        // directly to a `BuiltinType`; a user-defined simple base
2579        // already has its chain collapsed (each `SimpleType.builtin`
2580        // is the ultimate ancestor), so reading its `builtin` field
2581        // walks the full chain in one step.  Facets and whitespace
2582        // inherit too — XSD restriction semantics require the derived
2583        // type to satisfy every facet in the chain, so we seed
2584        // `facets` with the base's set and append the new ones below.
2585        //
2586        // Variety also inherits: restricting a `<xs:list>` type yields
2587        // a list type (length facets count items, item type is the
2588        // base's item type); restricting a `<xs:union>` yields a
2589        // union.  Without this, length=3 on a 3-item list would
2590        // (wrongly) check string length 3.
2591        //
2592        // Two valid forms per XSD §3.14.2:
2593        //   * `<xs:restriction base="…">`              — named base
2594        //   * `<xs:restriction> <xs:simpleType>…`      — inline anon base
2595        // The `base=` attribute and the inline `<xs:simpleType>` child
2596        // are mutually exclusive but one must be present.  Inline-base
2597        // form is handled when we encounter the `simpleType` child
2598        // below — it overrides the no-base initial defaults.
2599        //
2600        // Limitation: only handles bases that have already been parsed
2601        // (top-down schema declarations).  Forward references to
2602        // later-declared user types fall through to `BuiltinType::String`
2603        // and `Variety::Atomic` — declaration order matters.
2604        let mut base_builtin = BuiltinType::String;
2605        let mut whitespace = WhitespaceMode::Preserve;
2606        let mut facets = FacetSet::default();
2607        let mut variety: super::types::Variety = super::types::Variety::Atomic;
2608        let mut inherited = false;
2609        let mut has_named_base = false;
2610        let mut pending_forward_base: Option<QName> = None;
2611        // XSD 1.1 `<xs:assertion test="…">` facets on a simpleType
2612        // restriction — evaluated at validate time with `$value` bound
2613        // to the parsed atomic value.  Collected here, returned to
2614        // the caller alongside the other restriction outputs.
2615        let mut assertions: Vec<super::schema::Assertion> = Vec::new();
2616        if let Some(base_qn) = self.attr(attrs, "base") {
2617            has_named_base = true;
2618            let qn = self.parse_qname(base_qn, false)?;
2619            // XSD §3.14.6 cos-st-derived-ok — a simple type cannot
2620            // restrict itself.  Outside `<xs:redefine>` (which is the
2621            // one place same-name self-reference is the spec's
2622            // expansion semantics), reject this directly.
2623            if !self.in_redefine
2624                && self.simple_type_in_flight.as_ref() == Some(&qn)
2625            {
2626                return Err(self.err(format!(
2627                    "<xs:simpleType name={:?}><xs:restriction base={:?}>: \
2628                     a simple type cannot restrict itself (circular definition)",
2629                    qn.local, qn.local,
2630                )));
2631            }
2632            // Stash the resolved base name; the redefine-body validator
2633            // consults this to confirm src-redefine compliance.
2634            self.last_simple_restriction_base = Some(qn.clone());
2635            if qn.namespace.as_deref() == Some(XS) {
2636                // XSD §3.14.2 / §3.4.6 — only the BuiltinType set is
2637                // usable as a base.  xs:anyType (complex) and
2638                // xs:anySimpleType (the ultimate ancestor; not
2639                // restrictable per §3.16.7) are both forbidden, as is
2640                // anything not in our BuiltinType table at all.
2641                if qn.local.as_ref() == "anyType" {
2642                    return Err(self.err(
2643                        "<xs:restriction base=\"xs:anyType\"> is not allowed in a simpleType",
2644                    ));
2645                }
2646                if qn.local.as_ref() == "anySimpleType" {
2647                    return Err(self.err(
2648                        "<xs:restriction base=\"xs:anySimpleType\">: anySimpleType is \
2649                         the ultimate ancestor of all simple types and cannot itself \
2650                         be restricted (XSD §3.16.7)",
2651                    ));
2652                }
2653                match BuiltinType::from_name(&qn.local) {
2654                    Some(b) => {
2655                        // Gate XSD 1.1-only built-ins (dateTimeStamp,
2656                        // dayTimeDuration, yearMonthDuration,
2657                        // anyAtomicType, error) when the schema is
2658                        // being compiled in strict 1.0 mode.
2659                        if b.is_xsd11_only()
2660                            && !matches!(self.builder.effective_version, SchemaVersion::Xsd11)
2661                        {
2662                            return Err(self.err(format!(
2663                                "xs:{} is an XSD 1.1 built-in — set \
2664                                 SchemaOptions::version to Xsd11, or to Auto with \
2665                                 vc:minVersion=\"1.1\" on <xs:schema>",
2666                                qn.local,
2667                            )));
2668                        }
2669                        base_builtin = b;
2670                    }
2671                    None => return Err(self.err(format!(
2672                        "<xs:restriction base={base_qn:?}>: {base_qn:?} is not a built-in XSD type"
2673                    ))),
2674                }
2675            } else if let Some(TypeRef::Complex(_)) = self.builder.types.get(&qn) {
2676                return Err(self.err(format!(
2677                    "<xs:restriction base={base_qn:?}> in a simpleType must reference a simpleType, not a complexType"
2678                )));
2679            } else if let Some(TypeRef::Simple(st)) = self.builder.types.get(&qn) {
2680                if st.final_.contains(super::schema::BlockSet::RESTRICTION) {
2681                    return Err(self.err(format!(
2682                        "<xs:restriction base={base_qn:?}>: base simple type's \
2683                         `final` disallows restriction"
2684                    )));
2685                }
2686                base_builtin = st.builtin;
2687                facets = st.facets.clone();
2688                whitespace = st.whitespace;
2689                variety = st.variety.clone();
2690                inherited = true;
2691            } else if qn.namespace.as_deref() != self.current_target_ns.as_deref()
2692                && !import_covers(&self.builder.imported_namespaces,
2693                                  qn.namespace.as_deref())
2694            {
2695                // XSD §3.16.6 / src-resolve clause 4 — the base
2696                // resolves to a foreign namespace that the schema
2697                // didn't import.  Even if the namespace happened to
2698                // declare a same-named type elsewhere, we have no way
2699                // to reach it.  Reject loudly so the author fixes the
2700                // missing <xs:import>.
2701                return Err(self.err(format!(
2702                    "<xs:restriction base={base_qn:?}>: namespace {:?} is not the \
2703                     schema's targetNamespace and was not brought in by <xs:import>",
2704                    qn.namespace.as_deref().unwrap_or(""),
2705                )));
2706            } else {
2707                // Same-target-namespace forward reference — the base
2708                // hasn't been parsed yet, so we don't have its facets
2709                // to inherit.  Record the unresolved base so a post-
2710                // pass can splice in the real facets and re-run
2711                // `check_facet_tightening` on whatever this
2712                // restriction adds.
2713                pending_forward_base = Some(qn.clone());
2714            }
2715        }
2716        if !inherited {
2717            whitespace = base_builtin.default_whitespace();
2718        }
2719        let mut base_facet_count = facets.facets.len();
2720
2721        loop {
2722            match self.next_event()? {
2723                EventInto::StartElement { name } => {
2724                    let f_attrs = self.take_attrs()?;
2725                    self.push_ns_scope(&f_attrs);
2726                    let fqn = self.qname_of_element(&name)?;
2727                    if fqn.namespace.as_deref() == Some(XS) {
2728                        let v = self.attr(&f_attrs, "value");
2729                        match (fqn.local.as_ref(), v) {
2730                            ("length",        Some(v)) => facets.push(Facet::Length(v.parse().map_err(|e|
2731                                self.err(format!("length facet: {e}"))
2732                            )?)),
2733                            ("minLength",     Some(v)) => facets.push(Facet::MinLength(v.parse().map_err(|e|
2734                                self.err(format!("minLength facet: {e}"))
2735                            )?)),
2736                            ("maxLength",     Some(v)) => facets.push(Facet::MaxLength(v.parse().map_err(|e|
2737                                self.err(format!("maxLength facet: {e}"))
2738                            )?)),
2739                            ("pattern",       Some(v)) => {
2740                                let p = super::regex::Pattern::compile(v).map_err(|e|
2741                                    self.err(format!("pattern facet: {e}"))
2742                                )?;
2743                                facets.push(Facet::Pattern(p));
2744                            }
2745                            ("enumeration",   Some(v)) => {
2746                                // cvc-enumeration-valid: each enumeration
2747                                // value must lie in the base type's value
2748                                // space (XSD §3.14.6).
2749                                super::types::SimpleType::of_builtin(base_builtin)
2750                                    .validate(v)
2751                                    .map_err(|e| self.err(format!(
2752                                        "enumeration value {v:?} not in base type's value space: {}",
2753                                        e.message,
2754                                    )))?;
2755                                // Accumulate into one Enumeration facet.
2756                                if let Some(Facet::Enumeration(opts)) = facets.facets.last_mut() {
2757                                    opts.push(v.to_owned());
2758                                } else {
2759                                    facets.push(Facet::Enumeration(vec![v.to_owned()]));
2760                                }
2761                            }
2762                            ("whiteSpace",    Some(v)) => {
2763                                let new_ws = match v {
2764                                    "preserve" => WhitespaceMode::Preserve,
2765                                    "replace"  => WhitespaceMode::Replace,
2766                                    "collapse" => WhitespaceMode::Collapse,
2767                                    other => return Err(self.err(format!(
2768                                        "invalid whiteSpace value: {other:?}"
2769                                    ))),
2770                                };
2771                                // XSD §4.3.5 — whiteSpace facet may only be
2772                                // restricted further along the preserve →
2773                                // replace → collapse chain.  The base mode is
2774                                // whatever the parent (named or builtin)
2775                                // already imposes — not the builtin's default,
2776                                // because intermediate restrictions can tighten
2777                                // it (e.g. a user simpleType setting
2778                                // whiteSpace="replace" on xs:string).
2779                                let base_ws = whitespace;
2780                                let allowed = matches!(
2781                                    (base_ws, new_ws),
2782                                    (WhitespaceMode::Preserve, _)
2783                                    | (WhitespaceMode::Replace, WhitespaceMode::Replace | WhitespaceMode::Collapse)
2784                                    | (WhitespaceMode::Collapse, WhitespaceMode::Collapse)
2785                                );
2786                                if !allowed {
2787                                    return Err(self.err(format!(
2788                                        "whiteSpace={v:?} is not a valid restriction of the base whitespace mode"
2789                                    )));
2790                                }
2791                                whitespace = new_ws;
2792                            }
2793                            ("minInclusive",  Some(v)) => {
2794                                self.validate_bound_against_base(v, base_builtin, "minInclusive")?;
2795                                facets.push(Facet::MinInclusive(parse_bound(v, base_builtin)?));
2796                            }
2797                            ("maxInclusive",  Some(v)) => {
2798                                self.validate_bound_against_base(v, base_builtin, "maxInclusive")?;
2799                                facets.push(Facet::MaxInclusive(parse_bound(v, base_builtin)?));
2800                            }
2801                            ("minExclusive",  Some(v)) => {
2802                                self.validate_bound_against_base(v, base_builtin, "minExclusive")?;
2803                                facets.push(Facet::MinExclusive(parse_bound(v, base_builtin)?));
2804                            }
2805                            ("maxExclusive",  Some(v)) => {
2806                                self.validate_bound_against_base(v, base_builtin, "maxExclusive")?;
2807                                facets.push(Facet::MaxExclusive(parse_bound(v, base_builtin)?));
2808                            }
2809                            ("totalDigits",   Some(v)) => {
2810                                let n: u32 = v.parse().map_err(|e|
2811                                    self.err(format!("totalDigits: {e}"))
2812                                )?;
2813                                if n == 0 {
2814                                    return Err(self.err(
2815                                        "totalDigits value must be ≥ 1 (XSD §4.3.11)"
2816                                    ));
2817                                }
2818                                facets.push(Facet::TotalDigits(n));
2819                            }
2820                            ("fractionDigits", Some(v)) => {
2821                                let n: u32 = v.parse().map_err(|e|
2822                                    self.err(format!("fractionDigits: {e}"))
2823                                )?;
2824                                // XSD §3.3.13 / §3.3.14 — integer
2825                                // and its subtypes fix fractionDigits
2826                                // at 0; the derived value must equal
2827                                // the fixed base value.
2828                                if base_builtin.is_integer_family() && n != 0 {
2829                                    return Err(self.err(format!(
2830                                        "fractionDigits ({n}) cannot be nonzero on a derivation \
2831                                         of xs:integer (fixed at 0)"
2832                                    )));
2833                                }
2834                                facets.push(Facet::FractionDigits(n));
2835                            }
2836                            ("annotation", _) => {
2837                                self.parse_annotation_body(&f_attrs)?;
2838                                self.pop_ns_scope();
2839                                continue;
2840                            }
2841                            ("explicitTimezone", Some(v)) => {
2842                                // XSD 1.1 § 4.3.13 — values are
2843                                // `required` | `prohibited` | `optional`.
2844                                // Only legal in 1.1 mode.
2845                                if !matches!(self.builder.effective_version,
2846                                             SchemaVersion::Xsd11)
2847                                {
2848                                    return Err(self.err(
2849                                        "<xs:explicitTimezone> is an XSD 1.1 facet — \
2850                                         set SchemaOptions::version to Xsd11, or to Auto \
2851                                         with vc:minVersion=\"1.1\" on <xs:schema>",
2852                                    ));
2853                                }
2854                                use super::facets::TimezoneRequirement as TR;
2855                                let req = match v {
2856                                    "required"   => TR::Required,
2857                                    "prohibited" => TR::Prohibited,
2858                                    "optional"   => TR::Optional,
2859                                    other => return Err(self.err(format!(
2860                                        "<xs:explicitTimezone value={other:?}>: must be \
2861                                         \"required\", \"prohibited\", or \"optional\""
2862                                    ))),
2863                                };
2864                                facets.push(Facet::ExplicitTimezone(req));
2865                            }
2866                            ("length", None) | ("minLength", None) | ("maxLength", None)
2867                            | ("pattern", None) | ("enumeration", None) | ("whiteSpace", None)
2868                            | ("minInclusive", None) | ("maxInclusive", None)
2869                            | ("minExclusive", None) | ("maxExclusive", None)
2870                            | ("totalDigits", None) | ("fractionDigits", None)
2871                            | ("explicitTimezone", None) => {
2872                                return Err(self.err(format!(
2873                                    "<xs:{}> facet requires a 'value' attribute", fqn.local
2874                                )));
2875                            }
2876                            ("simpleType", _) => {
2877                                // Inline base for this restriction — XSD
2878                                // §3.14.2 lets `<xs:restriction>` carry an
2879                                // anonymous base via a child `simpleType`
2880                                // instead of `base="…"`. Only one inline
2881                                // simpleType is allowed, and not in
2882                                // combination with `base=`.
2883                                if has_named_base {
2884                                    return Err(self.err(
2885                                        "<xs:restriction> cannot combine base= with an inline <xs:simpleType>",
2886                                    ));
2887                                }
2888                                if inherited {
2889                                    return Err(self.err(
2890                                        "<xs:restriction> may have at most one inline <xs:simpleType>",
2891                                    ));
2892                                }
2893                                let inline_base = self.parse_simple_type_body(&f_attrs)?;
2894                                base_builtin = inline_base.builtin;
2895                                facets       = inline_base.facets.clone();
2896                                whitespace   = inline_base.whitespace;
2897                                variety      = inline_base.variety.clone();
2898                                inherited    = true;
2899                                base_facet_count = facets.facets.len();
2900                                self.pop_ns_scope();
2901                                continue;
2902                            }
2903                            ("assertion", _) => {
2904                                // XSD 1.1 simple-type assertion facet —
2905                                // `$value` is bound to the parsed
2906                                // atomic value at eval time.  Body is
2907                                // anno-only (we consume it inside
2908                                // `parse_assertion_body`); skip to
2909                                // matching EndElement.
2910                                assertions.extend(self.parse_assertion_body(&f_attrs)?);
2911                                self.pop_ns_scope();
2912                                continue;
2913                            }
2914                            (other, _) => {
2915                                return Err(self.err(format!(
2916                                    "<xs:{other}> is not a valid facet or child of <xs:restriction> (simpleType)"
2917                                )));
2918                            }
2919                        }
2920                    }
2921                    // Each facet body must be annotation-only — no
2922                    // nested xs:notation, xs:element, etc.
2923                    self.parse_anno_only_body(fqn.local.as_ref())?;
2924                    self.pop_ns_scope();
2925                }
2926                EventInto::EndElement { .. } => break,
2927                EventInto::Eof => return Err(self.err("unexpected EOF in restriction")),
2928                _ => {}
2929            }
2930        }
2931        if !has_named_base && !inherited {
2932            return Err(self.err(
2933                "<xs:restriction> needs either a base= attribute or an inline <xs:simpleType>",
2934            ));
2935        }
2936        // Derived bounds REPLACE inherited bounds of the "other"
2937        // inclusivity when both kinds end up in the merged set
2938        // (XSD §4.3.9 — minInclusive/minExclusive can't co-exist,
2939        // but inheritance of one then a derived override of the
2940        // other is the normal restriction pattern).
2941        prune_replaced_bounds(&mut facets, base_facet_count);
2942        self.validate_facet_set(&facets)?;
2943        // cvc-restriction (XSD §4.3): each derived facet must be a
2944        // legitimate tightening of the base's corresponding facet.
2945        // `facets.facets[..base_facet_count]` is the base set; the
2946        // tail is what this restriction adds.
2947        self.check_facet_tightening(&facets.facets[..base_facet_count], &facets.facets[base_facet_count..])?;
2948        // XSD §4.1.5 — applicable facets are per-variety:
2949        // * List: length, minLength, maxLength, pattern, enumeration, whiteSpace
2950        // * Union: pattern, enumeration
2951        // Anything else is a schema validity error.
2952        self.check_facet_applicability(&facets.facets[base_facet_count..], &variety)?;
2953        // If the named base wasn't resolvable at parse time
2954        // (same-target-namespace forward reference), remember the
2955        // tail of derived facets so the post-pass can validate them
2956        // against the eventually-resolved base.
2957        if let Some(base_qn) = pending_forward_base {
2958            let derived = facets.facets[base_facet_count..].to_vec();
2959            if !derived.is_empty() {
2960                self.builder.pending_simple_facet_checks.push((base_qn, derived));
2961            }
2962        }
2963        Ok((base_builtin, facets, whitespace, variety, assertions))
2964    }
2965
2966    fn check_facet_applicability(
2967        &self,
2968        derived: &[Facet],
2969        variety: &super::types::Variety,
2970    ) -> Result<(), SchemaCompileError> {
2971        use super::types::Variety;
2972        for f in derived {
2973            let allowed = match (variety, f) {
2974                (Variety::List { .. }, Facet::Length(_) | Facet::MinLength(_) | Facet::MaxLength(_)
2975                                     | Facet::Pattern(_) | Facet::Enumeration(_)) => true,
2976                (Variety::Union { .. }, Facet::Pattern(_) | Facet::Enumeration(_)) => true,
2977                (Variety::Atomic, _) => true,
2978                // List/Union with disallowed facet
2979                _ => false,
2980            };
2981            if !allowed {
2982                let kind = match variety {
2983                    Variety::List { .. } => "list",
2984                    Variety::Union { .. } => "union",
2985                    Variety::Atomic => "atomic",
2986                };
2987                let facet_name = match f {
2988                    Facet::Length(_)         => "length",
2989                    Facet::MinLength(_)      => "minLength",
2990                    Facet::MaxLength(_)      => "maxLength",
2991                    Facet::Pattern(_)        => "pattern",
2992                    Facet::Enumeration(_)    => "enumeration",
2993                    Facet::MinInclusive(_)   => "minInclusive",
2994                    Facet::MaxInclusive(_)   => "maxInclusive",
2995                    Facet::MinExclusive(_)   => "minExclusive",
2996                    Facet::MaxExclusive(_)   => "maxExclusive",
2997                    Facet::TotalDigits(_)    => "totalDigits",
2998                    Facet::FractionDigits(_) => "fractionDigits",
2999                    Facet::ExplicitTimezone(_) => "explicitTimezone",
3000                };
3001                return Err(self.err(format!(
3002                    "facet <xs:{facet_name}> is not applicable to a {kind} simple type"
3003                )));
3004            }
3005        }
3006        Ok(())
3007    }
3008
3009    // helper: see `prune_replaced_bounds` at the bottom of this module.
3010
3011    fn check_facet_tightening(
3012        &self, base: &[Facet], derived: &[Facet],
3013    ) -> Result<(), SchemaCompileError> {
3014        check_facet_tightening_pure(base, derived).map_err(|m| self.err(m))
3015    }
3016
3017    /// XSD §4.3 — every facet in a single restriction body must be
3018    /// consistent with the others. We enforce the cross-facet rules
3019    /// that don't depend on the base type's value space (those
3020    /// happen during `parse_bound` / `parse_value`).
3021    ///
3022    /// Rules enforced:
3023    /// * `length` is mutually exclusive with `minLength` / `maxLength`.
3024    /// * `minLength <= maxLength`.
3025    /// * `minInclusive` excludes `minExclusive` (same for max side).
3026    /// * `minInclusive <= maxInclusive`, `minExclusive <= maxExclusive`,
3027    ///   and mixed-bound forms.
3028    /// * `fractionDigits <= totalDigits`.
3029    fn validate_facet_set(&self, facets: &FacetSet) -> Result<(), SchemaCompileError> {
3030        let mut length:          Option<usize>  = None;
3031        let mut min_length:      Option<usize>  = None;
3032        let mut max_length:      Option<usize>  = None;
3033        let mut min_inclusive:   Option<&Bound> = None;
3034        let mut min_exclusive:   Option<&Bound> = None;
3035        let mut max_inclusive:   Option<&Bound> = None;
3036        let mut max_exclusive:   Option<&Bound> = None;
3037        let mut total_digits:    Option<u32>    = None;
3038        let mut fraction_digits: Option<u32>    = None;
3039        for f in &facets.facets {
3040            match f {
3041                Facet::Length(n)          => length          = Some(*n),
3042                Facet::MinLength(n)       => min_length      = Some(*n),
3043                Facet::MaxLength(n)       => max_length      = Some(*n),
3044                Facet::MinInclusive(b)    => min_inclusive   = Some(b),
3045                Facet::MinExclusive(b)    => min_exclusive   = Some(b),
3046                Facet::MaxInclusive(b)    => max_inclusive   = Some(b),
3047                Facet::MaxExclusive(b)    => max_exclusive   = Some(b),
3048                Facet::TotalDigits(n)     => total_digits    = Some(*n),
3049                Facet::FractionDigits(n)  => fraction_digits = Some(*n),
3050                _ => {}
3051            }
3052        }
3053        if length.is_some() && (min_length.is_some() || max_length.is_some()) {
3054            return Err(self.err(
3055                "length facet cannot co-occur with minLength or maxLength (XSD §4.3.1)",
3056            ));
3057        }
3058        if let (Some(lo), Some(hi)) = (min_length, max_length) {
3059            if lo > hi {
3060                return Err(self.err(format!(
3061                    "minLength ({lo}) > maxLength ({hi}) (XSD §4.3.2)"
3062                )));
3063            }
3064        }
3065        if min_inclusive.is_some() && min_exclusive.is_some() {
3066            return Err(self.err(
3067                "minInclusive and minExclusive are mutually exclusive (XSD §4.3.9)",
3068            ));
3069        }
3070        if max_inclusive.is_some() && max_exclusive.is_some() {
3071            return Err(self.err(
3072                "maxInclusive and maxExclusive are mutually exclusive (XSD §4.3.7)",
3073            ));
3074        }
3075        for (lo, hi, lo_name, hi_name) in [
3076            (min_inclusive, max_inclusive, "minInclusive", "maxInclusive"),
3077            (min_inclusive, max_exclusive, "minInclusive", "maxExclusive"),
3078            (min_exclusive, max_inclusive, "minExclusive", "maxInclusive"),
3079            (min_exclusive, max_exclusive, "minExclusive", "maxExclusive"),
3080        ] {
3081            if let (Some(lo), Some(hi)) = (lo, hi) {
3082                if compare_bounds(lo, hi).map(|o| o.is_gt()).unwrap_or(false) {
3083                    return Err(self.err(format!(
3084                        "{lo_name} > {hi_name} (XSD §4.3.x)"
3085                    )));
3086                }
3087            }
3088        }
3089        if let (Some(fd), Some(td)) = (fraction_digits, total_digits) {
3090            if fd > td {
3091                return Err(self.err(format!(
3092                    "fractionDigits ({fd}) > totalDigits ({td}) (XSD §4.3.12)"
3093                )));
3094            }
3095        }
3096        Ok(())
3097    }
3098
3099    fn validate_bound_against_base(
3100        &self, raw: &str, builtin: BuiltinType, facet: &str,
3101    ) -> Result<(), SchemaCompileError> {
3102        super::types::SimpleType::of_builtin(builtin)
3103            .validate(raw)
3104            .map_err(|e| self.err(format!(
3105                "{facet} value {raw:?} not in base type's value space: {}",
3106                e.message,
3107            )))?;
3108        Ok(())
3109    }
3110
3111    // ── top-level complexType ────────────────────────────────────────────
3112
3113    fn parse_top_complex_type(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
3114        // XSD §3.4.1 — a top-level <xs:complexType> requires `name`.
3115        if self.attr(attrs, "name").is_none() {
3116            return Err(self.err(
3117                "top-level <xs:complexType> requires a 'name' attribute",
3118            ));
3119        }
3120        let ct = self.parse_complex_type_body_inner(attrs, /*allow_name=*/true)?;
3121        if let Some(name) = &ct.name {
3122            // XSD §3.4.6 (ct-props-correct.1) — type names are unique
3123            // within the schema's name table.  `<xs:redefine>` is the
3124            // one exception: it intentionally replaces the original
3125            // type with the redefining version.
3126            if !self.in_redefine && self.builder.types.contains_key(name) {
3127                return Err(self.err(format!(
3128                    "duplicate top-level type definition {:?} \
3129                     (a type with this name is already declared)",
3130                    name.local,
3131                )));
3132            }
3133            self.builder.types.insert(name.clone(), TypeRef::Complex(Arc::new(ct)));
3134        }
3135        Ok(())
3136    }
3137
3138    fn parse_complex_type_body(&mut self, attrs: &[Attr<'a>])
3139        -> Result<ComplexType, SchemaCompileError>
3140    {
3141        self.parse_complex_type_body_inner(attrs, /*allow_name=*/false)
3142    }
3143
3144    fn parse_complex_type_body_inner(&mut self, attrs: &[Attr<'a>], allow_name: bool)
3145        -> Result<ComplexType, SchemaCompileError>
3146    {
3147        if !allow_name && self.attr(attrs, "name").is_some() {
3148            return Err(self.err(
3149                "inline <xs:complexType> must not have a 'name' attribute (XSD §3.4.2)",
3150            ));
3151        }
3152        let name = self.attr(attrs, "name").map(|n| QName {
3153            namespace: self.current_target_ns.clone(),
3154            local:     Arc::from(n),
3155        });
3156        let abstract_ = self.parse_xsd_bool(attrs, "abstract")?.unwrap_or(false);
3157        let mixed_attr = self.parse_xsd_bool(attrs, "mixed")?.unwrap_or(false);
3158        // XSD §3.1 — fall back to schema-level blockDefault /
3159        // finalDefault when this complexType doesn't carry its own.
3160        // For complexType these accept only restriction/extension/#all
3161        // (substitution belongs to xs:element), so mask the inherited
3162        // default to those bits.
3163        let ct_mask = BlockSet::RESTRICTION | BlockSet::EXTENSION;
3164        let block  = match self.attr(attrs, "block") {
3165            Some(_) => parse_ct_derivation_set(self.attr(attrs, "block"), "block")?,
3166            None    => self.block_default & ct_mask,
3167        };
3168        let final_ = match self.attr(attrs, "final") {
3169            Some(_) => parse_ct_derivation_set(self.attr(attrs, "final"), "final")?,
3170            None    => self.final_default & ct_mask,
3171        };
3172
3173        let mut content   = ContentModel::Empty;
3174        let mut attr_uses = Vec::new();
3175        let mut any_attr  = None;
3176        let mut derivation: Option<Derivation> = None;
3177        let mut mixed     = mixed_attr;
3178        let mut pending_ag_refs: Vec<QName> = Vec::new();
3179        let mut assertions: Vec<super::schema::Assertion> = Vec::new();
3180        let mut seen_anno = false;
3181        let mut seen_other = false;
3182        // XSD §3.4.2 body grammar:
3183        //   (annotation?, (simpleContent | complexContent
3184        //                  | ((group|all|choice|sequence)?,
3185        //                     ((attribute | attributeGroup)*,
3186        //                      anyAttribute?))))
3187        // i.e. simpleContent/complexContent are mutually exclusive
3188        // with each other and with any model-group/attribute child;
3189        // the implicit form allows at most one model group, then
3190        // attribute uses, then at most one anyAttribute. These flags
3191        // enforce that ordering.
3192        let mut saw_derived_content = false;
3193        let mut saw_model_group     = false;
3194        let mut saw_any_attribute   = false;
3195
3196        loop {
3197            match self.next_event()? {
3198                EventInto::StartElement { name: child } => {
3199                    let child_attrs = self.take_attrs()?;
3200                    self.push_ns_scope(&child_attrs);
3201                    let qn = self.qname_of_element(&child)?;
3202                    if qn.namespace.as_deref() == Some(XS) {
3203                        self.check_annotation_pos(
3204                            &qn.local, &mut seen_anno, &mut seen_other, "complexType",
3205                        )?;
3206                        let local = qn.local.as_ref();
3207                        if saw_derived_content && local != "annotation" {
3208                            return Err(self.err(format!(
3209                                "<xs:complexType> with <xs:simpleContent>/<xs:complexContent> \
3210                                 cannot have <xs:{local}> as a sibling",
3211                            )));
3212                        }
3213                        match local {
3214                            "sequence" | "choice" | "all" => {
3215                                if saw_model_group || saw_any_attribute
3216                                    || !attr_uses.is_empty()
3217                                {
3218                                    return Err(self.err(
3219                                        "<xs:complexType> body: model group must precede \
3220                                         attribute / attributeGroup / anyAttribute, and \
3221                                         only one model group is allowed",
3222                                    ));
3223                                }
3224                                saw_model_group = true;
3225                                let kind = match qn.local.as_ref() {
3226                                    "sequence" => GroupKind::Sequence,
3227                                    "choice"   => GroupKind::Choice,
3228                                    _          => GroupKind::All,
3229                                };
3230                                let particle = self.parse_group_body(kind, &child_attrs)?;
3231                                content = ContentModel::Complex { root: particle, mixed };
3232                            }
3233                            "group" => {
3234                                if saw_model_group || saw_any_attribute
3235                                    || !attr_uses.is_empty()
3236                                {
3237                                    return Err(self.err(
3238                                        "<xs:complexType> body: model group must precede \
3239                                         attribute / attributeGroup / anyAttribute, and \
3240                                         only one model group is allowed",
3241                                    ));
3242                                }
3243                                saw_model_group = true;
3244                                // XSD 1.0 §3.4.2: a `<xs:complexType>` body
3245                                // may use `<xs:group ref="G"/>` directly as
3246                                // its single top-level particle (instead of
3247                                // wrapping a sequence/choice/all). The `ref`
3248                                // attribute is required — a nested
3249                                // `<xs:group name=…>` definition is not
3250                                // allowed here.
3251                                let r = self.attr(&child_attrs, "ref").ok_or_else(|| self.err(
3252                                    "<xs:group> inside <xs:complexType> must use ref= \
3253                                     (nested group definitions are not allowed)",
3254                                ))?;
3255                                {
3256                                    let ref_qn = self.parse_qname(r, false)?;
3257                                    let g_min = parse_min_occurs(self.attr(&child_attrs, "minOccurs"))?;
3258                                    let g_max = parse_max_occurs(self.attr(&child_attrs, "maxOccurs"))?;
3259                                    check_occurs(g_min, g_max)?;
3260                                    content = ContentModel::Complex {
3261                                        root: Particle {
3262                                            min_occurs: g_min,
3263                                            max_occurs: g_max,
3264                                            term:       Term::GroupRef(ref_qn),
3265                                        },
3266                                        mixed,
3267                                    };
3268                                }
3269                                self.skip_body()?;
3270                            }
3271                            "attribute" => {
3272                                if saw_any_attribute {
3273                                    return Err(self.err(
3274                                        "<xs:attribute> cannot follow <xs:anyAttribute> in <xs:complexType>",
3275                                    ));
3276                                }
3277                                let au = self.parse_local_attribute_use(&child_attrs)?;
3278                                attr_uses.push(au);
3279                            }
3280                            "attributeGroup" => {
3281                                if saw_any_attribute {
3282                                    return Err(self.err(
3283                                        "<xs:attributeGroup> cannot follow <xs:anyAttribute> in <xs:complexType>",
3284                                    ));
3285                                }
3286                                let r = self.attr(&child_attrs, "ref").ok_or_else(|| self.err(
3287                                    "<xs:attributeGroup> inside <xs:complexType> must use ref= \
3288                                     (nested attributeGroup definitions are not allowed)",
3289                                ))?;
3290                                let qn = self.parse_qname(r, false)?;
3291                                match self.builder.attr_groups.get(&qn) {
3292                                    Some(ag) => {
3293                                        for au in &ag.attributes {
3294                                            attr_uses.push(au.clone());
3295                                        }
3296                                        // XSD §3.10.6 — multiple
3297                                        // attributeGroup wildcards in
3298                                        // one complex type INTERSECT.
3299                                        any_attr = merge_any_intersect(any_attr, ag.any.clone());
3300                                    }
3301                                    None => pending_ag_refs.push(qn),
3302                                }
3303                                // XSD §3.6.2 — ref-form attributeGroup's
3304                                // body may only contain <xs:annotation>.
3305                                self.parse_anno_only_body("attributeGroup")?;
3306                            }
3307                            "anyAttribute" => {
3308                                if saw_any_attribute {
3309                                    return Err(self.err(
3310                                        "<xs:complexType> body: at most one <xs:anyAttribute>",
3311                                    ));
3312                                }
3313                                saw_any_attribute = true;
3314                                check_no_occurs(&child_attrs, "anyAttribute")?;
3315                                any_attr = Some(self.parse_wildcard(&child_attrs)?);
3316                                self.parse_anno_only_body("anyAttribute")?;
3317                            }
3318                            "complexContent" | "simpleContent" => {
3319                                if saw_model_group || saw_any_attribute
3320                                    || !attr_uses.is_empty()
3321                                {
3322                                    return Err(self.err(format!(
3323                                        "<xs:{local}> cannot co-occur with model group, \
3324                                         attribute, attributeGroup, or anyAttribute in <xs:complexType>",
3325                                    )));
3326                                }
3327                                saw_derived_content = true;
3328                                let (deriv, inner_content, inner_attrs, inner_any, inner_mixed, inner_pending) =
3329                                    self.parse_derived_content(&child_attrs, qn.local.as_ref() == "simpleContent")?;
3330                                derivation = deriv;
3331                                content    = inner_content;
3332                                attr_uses.extend(inner_attrs);
3333                                if inner_any.is_some() { any_attr = inner_any; }
3334                                mixed = inner_mixed.unwrap_or(mixed);
3335                                // Propagate the resolved mixed flag into
3336                                // the derived content's mixed bit — the
3337                                // restriction/extension body always
3338                                // returns mixed=false on Complex content
3339                                // because it doesn't know the outer
3340                                // context.
3341                                if let ContentModel::Complex { mixed: ref mut m, .. } = content {
3342                                    *m = mixed;
3343                                }
3344                                pending_ag_refs.extend(inner_pending);
3345                            }
3346                            "annotation" => self.parse_annotation_body(&child_attrs)?,
3347                            // XSD 1.1 `<xs:assert>` — an XPath 2.0
3348                            // assertion evaluated against this type's
3349                            // instance elements at validate time.
3350                            "assert" => assertions.extend(self.parse_assertion_body(&child_attrs)?),
3351                            other => return Err(self.err(format!(
3352                                "<xs:{other}> is not allowed as a child of <xs:complexType>"
3353                            ))),
3354                        }
3355                    } else {
3356                        self.skip_body()?;
3357                    }
3358                    self.pop_ns_scope();
3359                }
3360                EventInto::EndElement { .. } => break,
3361                EventInto::Eof => return Err(self.err("unexpected EOF in complexType")),
3362                _ => {}
3363            }
3364        }
3365        // ct-props-correct (XSD §3.4.6): a complex type's attribute
3366        // uses must have pairwise-distinct names. Forward refs to
3367        // attribute groups skip this check because the placeholder
3368        // we use until resolution carries no resolved name.
3369        let mut seen_attr_names: HashSet<QName> = HashSet::new();
3370        for au in &attr_uses {
3371            if !seen_attr_names.insert(au.decl.name.clone()) {
3372                return Err(self.err(format!(
3373                    "<xs:complexType{}> declares attribute {:?} more than once",
3374                    name.as_ref().map(|n| format!(" name={:?}", n.local)).unwrap_or_default(),
3375                    au.decl.name.local,
3376                )));
3377            }
3378        }
3379        // ct-id-checks (XSD §3.4.6): a complex type may have at most
3380        // one attribute whose type is xs:ID (or derived from it). The
3381        // ID uniqueness constraint applies per element instance, so
3382        // two ID-typed attributes on the same element would always
3383        // collide.
3384        let id_count = attr_uses.iter().filter(|au| {
3385            matches!(au.decl.type_def.builtin, BuiltinType::Id)
3386        }).count();
3387        if id_count > 1 {
3388            return Err(self.err(format!(
3389                "<xs:complexType{}> declares {id_count} attributes of type xs:ID; \
3390                 at most one is allowed (XSD §3.4.6)",
3391                name.as_ref().map(|n| format!(" name={:?}", n.local)).unwrap_or_default(),
3392            )));
3393        }
3394
3395        // XSD §3.4.2 — a complexType with `mixed="true"` but no
3396        // explicit content (no sequence/choice/all/simpleContent/
3397        // complexContent) behaves as a mixed-content type whose
3398        // element model is empty. Promote the content from Empty
3399        // to Complex{empty sequence, mixed} so the validator
3400        // accepts text but no child elements.
3401        let content = if mixed && matches!(content, ContentModel::Empty) {
3402            ContentModel::Complex {
3403                root: Particle {
3404                    min_occurs: 1,
3405                    max_occurs: MaxOccurs::Bounded(1),
3406                    term: Term::Group {
3407                        kind: GroupKind::Sequence,
3408                        particles: Arc::from(Vec::<Particle>::new()),
3409                    },
3410                },
3411                mixed: true,
3412            }
3413        } else {
3414            content
3415        };
3416
3417        Ok(ComplexType {
3418            name,
3419            derivation,
3420            content,
3421            matcher: std::sync::OnceLock::new(),
3422            attributes: attr_uses,
3423            any_attribute: any_attr,
3424            abstract_,
3425            block,
3426            final_,
3427            pending_attribute_group_refs: pending_ag_refs,
3428            assertions,
3429        })
3430    }
3431
3432    /// Parse the body of `<xs:complexContent>` or `<xs:simpleContent>`,
3433    /// which always wraps a single `<xs:restriction>` or `<xs:extension>`.
3434    fn parse_derived_content(
3435        &mut self,
3436        outer_attrs: &[Attr<'a>],
3437        is_simple_content: bool,
3438    ) -> Result<
3439        (Option<Derivation>, ContentModel, Vec<AttributeUse>, Option<Wildcard>, Option<bool>, Vec<QName>),
3440        SchemaCompileError,
3441    > {
3442        let mut deriv: Option<Derivation> = None;
3443        let mut content = ContentModel::Empty;
3444        let mut attrs = Vec::new();
3445        let mut any   = None;
3446        // XSD §3.4.2 — `mixed=` may be set on either the outer
3447        // <xs:complexType> or on <xs:complexContent>; the latter
3448        // overrides.  `<xs:simpleContent>` doesn't allow mixed at all.
3449        let mut mixed: Option<bool> = if is_simple_content {
3450            None
3451        } else {
3452            self.parse_xsd_bool(outer_attrs, "mixed")?
3453        };
3454        let mut pending_ag_refs: Vec<QName> = Vec::new();
3455        let parent = if is_simple_content { "simpleContent" } else { "complexContent" };
3456        // XSD §3.4.2 — body of <xs:simpleContent>/<xs:complexContent> is
3457        // (annotation?, (restriction | extension)). Exactly one of the
3458        // two derivations is required, annotation must come first.
3459        let mut seen_anno = false;
3460        let mut seen_other = false;
3461        let mut saw_derivation = false;
3462
3463        loop {
3464            match self.next_event()? {
3465                EventInto::StartElement { name } => {
3466                    let inner_attrs = self.take_attrs()?;
3467                    self.push_ns_scope(&inner_attrs);
3468                    let qn = self.qname_of_element(&name)?;
3469                    if qn.namespace.as_deref() == Some(XS) {
3470                        self.check_annotation_pos(
3471                            &qn.local, &mut seen_anno, &mut seen_other, parent,
3472                        )?;
3473                        let local = qn.local.as_ref();
3474                        match local {
3475                            "restriction" | "extension" => {
3476                                if saw_derivation {
3477                                    return Err(self.err(format!(
3478                                        "<xs:{parent}> must contain exactly one \
3479                                         <xs:restriction> or <xs:extension>",
3480                                    )));
3481                                }
3482                                saw_derivation = true;
3483                                let method = if local == "restriction" {
3484                                    DerivationMethod::Restriction
3485                                } else { DerivationMethod::Extension };
3486                                let base = self.attr(&inner_attrs, "base")
3487                                    .ok_or_else(|| self.err(format!(
3488                                        "missing 'base' on <xs:{local}>"
3489                                    )))?;
3490                                let base_qn = self.parse_qname(base, false)?;
3491                                // XSD §3.4.2 — xs:anyType has *complex*
3492                                // content, so it cannot be the base of a
3493                                // <xs:simpleContent> derivation.
3494                                if is_simple_content
3495                                    && base_qn.namespace.as_deref() == Some(XS)
3496                                    && base_qn.local.as_ref() == "anyType"
3497                                {
3498                                    return Err(self.err(
3499                                        "<xs:simpleContent> cannot derive from xs:anyType \
3500                                         (it has complex content, not simple)",
3501                                    ));
3502                                }
3503                                // XSD §3.4.2 — `<xs:simpleContent><xs:restriction>`
3504                                // requires a base whose own content is
3505                                // simple-content (a complex type), so the
3506                                // derived restriction can tighten that
3507                                // simple body.  Restricting a built-in
3508                                // simple type directly skips the carrier
3509                                // complex type entirely and isn't allowed
3510                                // (use `<xs:extension>` for that case).
3511                                if is_simple_content
3512                                    && matches!(method, DerivationMethod::Restriction)
3513                                    && base_qn.namespace.as_deref() == Some(XS)
3514                                    && base_qn.local.as_ref() != "anyType"
3515                                {
3516                                    return Err(self.err(format!(
3517                                        "<xs:simpleContent><xs:restriction base={:?}> — \
3518                                         built-in xs:* types are simple types and cannot \
3519                                         be restricted via <xs:simpleContent> (use \
3520                                         <xs:extension>, or restrict via <xs:simpleType>)",
3521                                        format!("xs:{}", base_qn.local),
3522                                    )));
3523                                }
3524                                deriv = Some(Derivation {
3525                                    method,
3526                                    base: self.type_ref_for(base_qn),
3527                                });
3528
3529                                let (inner_content, inner_attrs2, inner_any, inner_mixed, inner_pending) =
3530                                    self.parse_derivation_body(is_simple_content, method)?;
3531                                content = inner_content;
3532                                attrs.extend(inner_attrs2);
3533                                any = inner_any;
3534                                // parse_derivation_body returns mixed=None,
3535                                // but `<xs:complexContent mixed=…>` already
3536                                // captured the user's choice on the outer
3537                                // element — don't overwrite it with None.
3538                                if let Some(m) = inner_mixed { mixed = Some(m); }
3539                                pending_ag_refs.extend(inner_pending);
3540                            }
3541                            "annotation" => self.parse_annotation_body(&inner_attrs)?,
3542                            other => return Err(self.err(format!(
3543                                "<xs:{other}> is not allowed as a child of <xs:{parent}>"
3544                            ))),
3545                        }
3546                    } else {
3547                        self.skip_body()?;
3548                    }
3549                    self.pop_ns_scope();
3550                }
3551                EventInto::EndElement { .. } => break,
3552                EventInto::Eof => return Err(self.err("unexpected EOF in derived content")),
3553                _ => {}
3554            }
3555        }
3556        if !saw_derivation {
3557            return Err(self.err(format!(
3558                "<xs:{parent}> must contain a <xs:restriction> or <xs:extension>"
3559            )));
3560        }
3561        Ok((deriv, content, attrs, any, mixed, pending_ag_refs))
3562    }
3563
3564    fn parse_derivation_body(
3565        &mut self,
3566        is_simple_content: bool,
3567        method:            DerivationMethod,
3568    ) -> Result<(ContentModel, Vec<AttributeUse>, Option<Wildcard>, Option<bool>, Vec<QName>), SchemaCompileError>
3569    {
3570        let mut content = if is_simple_content {
3571            ContentModel::Simple(Arc::new(SimpleType::of_builtin(BuiltinType::String)))
3572        } else {
3573            ContentModel::Empty
3574        };
3575        let mut attrs = Vec::new();
3576        let mut any   = None;
3577        let mixed     = None;
3578        let mut pending_ag_refs: Vec<QName> = Vec::new();
3579        // XSD §3.4.2: an <xs:extension> or <xs:restriction> inside a
3580        // <xs:complexContent> contains at most one particle (group |
3581        // all | choice | sequence) and then attribute uses.
3582        let mut saw_particle = false;
3583        let mut saw_any_attribute = false;
3584        let mut saw_inline_simple_type = false;
3585        let mut seen_anno = false;
3586        let mut seen_other = false;
3587
3588        loop {
3589            match self.next_event()? {
3590                EventInto::StartElement { name } => {
3591                    let child_attrs = self.take_attrs()?;
3592                    self.push_ns_scope(&child_attrs);
3593                    let qn = self.qname_of_element(&name)?;
3594                    if qn.namespace.as_deref() == Some(XS) {
3595                        self.check_annotation_pos(
3596                            &qn.local, &mut seen_anno, &mut seen_other,
3597                            if is_simple_content { "simpleContent derivation" }
3598                            else { "complexContent derivation" },
3599                        )?;
3600                        match qn.local.as_ref() {
3601                            "sequence" | "choice" | "all" | "group" => {
3602                                if is_simple_content {
3603                                    return Err(self.err(format!(
3604                                        "<xs:simpleContent>'s {} body has no place for an \
3605                                         <xs:{}> model group (simple content's body is text)",
3606                                         if method == DerivationMethod::Extension { "extension" }
3607                                         else { "restriction" },
3608                                         qn.local,
3609                                    )));
3610                                }
3611                                if saw_particle || saw_any_attribute || !attrs.is_empty() {
3612                                    return Err(self.err(
3613                                        "<xs:extension>/<xs:restriction> body: at most one \
3614                                         model-group particle, and it must precede attributes",
3615                                    ));
3616                                }
3617                                saw_particle = true;
3618                                match qn.local.as_ref() {
3619                                    "group" => {
3620                                        let r = self.attr(&child_attrs, "ref").ok_or_else(|| self.err(
3621                                            "<xs:group> inside <xs:extension>/<xs:restriction> must use ref=",
3622                                        ))?;
3623                                        let ref_qn = self.parse_qname(r, false)?;
3624                                        let g_min = parse_min_occurs(self.attr(&child_attrs, "minOccurs"))?;
3625                                        let g_max = parse_max_occurs(self.attr(&child_attrs, "maxOccurs"))?;
3626                                        check_occurs(g_min, g_max)?;
3627                                        content = ContentModel::Complex {
3628                                            root: Particle {
3629                                                min_occurs: g_min,
3630                                                max_occurs: g_max,
3631                                                term:       Term::GroupRef(ref_qn),
3632                                            },
3633                                            mixed: false,
3634                                        };
3635                                        self.skip_body()?;
3636                                    }
3637                                    _ => {
3638                                        let kind = match qn.local.as_ref() {
3639                                            "sequence" => GroupKind::Sequence,
3640                                            "choice"   => GroupKind::Choice,
3641                                            _          => GroupKind::All,
3642                                        };
3643                                        let particle = self.parse_group_body(kind, &child_attrs)?;
3644                                        content = ContentModel::Complex { root: particle, mixed: false };
3645                                    }
3646                                }
3647                            }
3648                            "attribute" => {
3649                                if saw_any_attribute {
3650                                    return Err(self.err(
3651                                        "<xs:attribute> cannot follow <xs:anyAttribute>",
3652                                    ));
3653                                }
3654                                attrs.push(self.parse_local_attribute_use(&child_attrs)?);
3655                            }
3656                            "attributeGroup" => {
3657                                if saw_any_attribute {
3658                                    return Err(self.err(
3659                                        "<xs:attributeGroup> cannot follow <xs:anyAttribute>",
3660                                    ));
3661                                }
3662                                let r = self.attr(&child_attrs, "ref").ok_or_else(|| self.err(
3663                                    "<xs:attributeGroup> inside <xs:extension>/<xs:restriction> \
3664                                     must use ref= (no nested definitions)",
3665                                ))?;
3666                                let qn = self.parse_qname(r, false)?;
3667                                match self.builder.attr_groups.get(&qn) {
3668                                    Some(ag) => {
3669                                        for au in &ag.attributes {
3670                                            attrs.push(au.clone());
3671                                        }
3672                                        any = merge_any_intersect(any, ag.any.clone());
3673                                    }
3674                                    None => pending_ag_refs.push(qn),
3675                                }
3676                                self.parse_anno_only_body("attributeGroup")?;
3677                            }
3678                            "anyAttribute" => {
3679                                if saw_any_attribute {
3680                                    return Err(self.err(
3681                                        "at most one <xs:anyAttribute> in this derivation body",
3682                                    ));
3683                                }
3684                                saw_any_attribute = true;
3685                                check_no_occurs(&child_attrs, "anyAttribute")?;
3686                                any = Some(self.parse_wildcard(&child_attrs)?);
3687                                self.parse_anno_only_body("anyAttribute")?;
3688                            }
3689                            "annotation" => self.parse_annotation_body(&child_attrs)?,
3690                            // Inside <xs:simpleContent><xs:restriction>, facet
3691                            // children (length, minInclusive, …) and an
3692                            // optional inline <xs:simpleType> base are also
3693                            // allowed (XSD §3.4.2). We skip their bodies
3694                            // here — facet semantics for simple-content
3695                            // restriction are picked up by `simple_type_for`
3696                            // on the base type. Accepted so the schema
3697                            // compiles; the more elaborate "derived facets
3698                            // must restrict base facets" check is out of
3699                            // scope for this layer.
3700                            // XSD §3.4.2 — inline simpleType and facets are
3701                            // only valid inside <xs:simpleContent>'s
3702                            // <xs:restriction>. <xs:extension> in either
3703                            // simple- or complex-content has no place for
3704                            // facets or inline simpleType bodies.
3705                            "simpleType" | "length" | "minLength" | "maxLength"
3706                            | "minInclusive" | "minExclusive" | "maxInclusive"
3707                            | "maxExclusive" | "totalDigits" | "fractionDigits"
3708                            | "enumeration" | "pattern" | "whiteSpace"
3709                                if is_simple_content
3710                                    && method == DerivationMethod::Restriction =>
3711                            {
3712                                // XSD §3.4.2 — simpleContent restriction's
3713                                // body grammar is
3714                                //   (annotation?, simpleType?, facet*,
3715                                //    (attribute | attributeGroup)*,
3716                                //    anyAttribute?)
3717                                // so inline simpleType and facets must
3718                                // precede any attribute uses.
3719                                if !attrs.is_empty() || saw_any_attribute {
3720                                    return Err(self.err(format!(
3721                                        "<xs:{}> in a <xs:simpleContent><xs:restriction> \
3722                                         must precede attribute declarations",
3723                                        qn.local,
3724                                    )));
3725                                }
3726                                if qn.local.as_ref() == "simpleType" {
3727                                    if saw_inline_simple_type {
3728                                        return Err(self.err(
3729                                            "<xs:simpleContent><xs:restriction> body \
3730                                             may have at most one inline <xs:simpleType>",
3731                                        ));
3732                                    }
3733                                    saw_inline_simple_type = true;
3734                                }
3735                                self.skip_body()?;
3736                            }
3737                            other => return Err(self.err(format!(
3738                                "<xs:{other}> is not allowed in this derivation body"
3739                            ))),
3740                        }
3741                    } else {
3742                        self.skip_body()?;
3743                    }
3744                    self.pop_ns_scope();
3745                }
3746                EventInto::EndElement { .. } => break,
3747                EventInto::Eof => return Err(self.err("unexpected EOF in derivation body")),
3748                _ => {}
3749            }
3750        }
3751        Ok((content, attrs, any, mixed, pending_ag_refs))
3752    }
3753
3754    // ── particle group body (sequence/choice/all) ────────────────────────
3755
3756    fn parse_group_body(&mut self, kind: GroupKind, attrs: &[Attr<'a>])
3757        -> Result<Particle, SchemaCompileError>
3758    {
3759        let min_occurs = parse_min_occurs(self.attr(attrs, "minOccurs"))?;
3760        let max_occurs = parse_max_occurs(self.attr(attrs, "maxOccurs"))?;
3761        check_occurs(min_occurs, max_occurs)?;
3762        // XSD 1.0 § 3.8.6 — <xs:all> has minOccurs ∈ {0,1} and
3763        // maxOccurs MUST be 1.  XSD 1.1 § 3.8.6 relaxed maxOccurs
3764        // to allow any non-negative value (or unbounded); minOccurs
3765        // is still bounded by maxOccurs.  Apply the 1.0 cap only
3766        // when the effective version is 1.0.
3767        if matches!(kind, GroupKind::All) {
3768            let strict_10 = !matches!(
3769                self.builder.effective_version, SchemaVersion::Xsd11
3770            );
3771            if strict_10 {
3772                if min_occurs > 1 {
3773                    return Err(self.err(
3774                        "<xs:all> minOccurs must be 0 or 1 (XSD 1.0 §3.8.6) — \
3775                         set SchemaOptions::version to Xsd11 to relax",
3776                    ));
3777                }
3778                if max_occurs != MaxOccurs::Bounded(1) {
3779                    return Err(self.err(
3780                        "<xs:all> maxOccurs must be exactly 1 (XSD 1.0 §3.8.6) — \
3781                         set SchemaOptions::version to Xsd11 to relax",
3782                    ));
3783                }
3784            }
3785        }
3786        let parent_name = match kind {
3787            GroupKind::Sequence => "sequence",
3788            GroupKind::Choice   => "choice",
3789            GroupKind::All      => "all",
3790        };
3791        // Sequence/choice/all don't accept a `name` attribute (only
3792        // top-level <xs:group> names a model group; the nested
3793        // particle groups inherit no naming).
3794        if self.attr(attrs, "name").is_some() {
3795            return Err(self.err(format!(
3796                "<xs:{parent_name}> does not take a 'name' attribute"
3797            )));
3798        }
3799        let mut particles = Vec::new();
3800        let mut seen_anno = false;
3801        let mut seen_other = false;
3802        loop {
3803            match self.next_event()? {
3804                EventInto::StartElement { name } => {
3805                    let child_attrs = self.take_attrs()?;
3806                    self.push_ns_scope(&child_attrs);
3807                    let qn = self.qname_of_element(&name)?;
3808                    if qn.namespace.as_deref() == Some(XS) {
3809                        self.check_annotation_pos(
3810                            &qn.local, &mut seen_anno, &mut seen_other, parent_name,
3811                        )?;
3812                        // XSD §3.8.6 — <xs:all> body may only contain
3813                        // <xs:element> (no nested groups, choices,
3814                        // sequences, or wildcards).
3815                        if matches!(kind, GroupKind::All)
3816                            && !matches!(qn.local.as_ref(), "element" | "annotation")
3817                        {
3818                            return Err(self.err(format!(
3819                                "<xs:all> body may only contain <xs:element> children, found <xs:{}>",
3820                                qn.local,
3821                            )));
3822                        }
3823                        match qn.local.as_ref() {
3824                            "element" => {
3825                                let p = self.parse_local_element_particle(&child_attrs)?;
3826                                // XSD 1.0 § 3.8.6 cos-all-particle — an
3827                                // element particle directly inside
3828                                // <xs:all> must have maxOccurs ∈ {0,1}.
3829                                if matches!(kind, GroupKind::All)
3830                                    && !matches!(
3831                                        self.builder.effective_version,
3832                                        SchemaVersion::Xsd11
3833                                    )
3834                                    && !matches!(p.max_occurs, MaxOccurs::Bounded(0)
3835                                                              | MaxOccurs::Bounded(1))
3836                                {
3837                                    return Err(self.err(
3838                                        "<xs:all> element particle must have maxOccurs ∈ {0,1} \
3839                                         (XSD 1.0 §3.8.6 cos-all-particle) — set \
3840                                         SchemaOptions::version to Xsd11 to relax",
3841                                    ));
3842                                }
3843                                particles.push(p);
3844                            }
3845                            "sequence" | "choice" | "all" => {
3846                                let inner_kind = match qn.local.as_ref() {
3847                                    "sequence" => GroupKind::Sequence,
3848                                    "choice"   => GroupKind::Choice,
3849                                    _          => GroupKind::All,
3850                                };
3851                                particles.push(self.parse_group_body(inner_kind, &child_attrs)?);
3852                            }
3853                            "group" => {
3854                                let r = self.attr(&child_attrs, "ref").ok_or_else(|| self.err(
3855                                    "<xs:group> inside a model group must use ref= \
3856                                     (nested group definitions are not allowed)",
3857                                ))?;
3858                                let ref_qn = self.parse_qname(r, false)?;
3859                                let g_min = parse_min_occurs(self.attr(&child_attrs, "minOccurs"))?;
3860                                let g_max = parse_max_occurs(self.attr(&child_attrs, "maxOccurs"))?;
3861                                check_occurs(g_min, g_max)?;
3862                                particles.push(Particle {
3863                                    min_occurs: g_min,
3864                                    max_occurs: g_max,
3865                                    term: Term::GroupRef(ref_qn),
3866                                });
3867                                self.skip_body()?;
3868                            }
3869                            "any" => {
3870                                let any_min = parse_min_occurs(self.attr(&child_attrs, "minOccurs"))?;
3871                                let any_max = parse_max_occurs(self.attr(&child_attrs, "maxOccurs"))?;
3872                                check_occurs(any_min, any_max)?;
3873                                particles.push(Particle {
3874                                    min_occurs: any_min,
3875                                    max_occurs: any_max,
3876                                    term: Term::Wildcard(self.parse_wildcard(&child_attrs)?),
3877                                });
3878                                self.parse_anno_only_body("any")?;
3879                            }
3880                            "annotation" => self.parse_annotation_body(&child_attrs)?,
3881                            other => return Err(self.err(format!(
3882                                "<xs:{other}> is not allowed as a child of <xs:{parent_name}>"
3883                            ))),
3884                        }
3885                    } else {
3886                        self.skip_body()?;
3887                    }
3888                    self.pop_ns_scope();
3889                }
3890                EventInto::EndElement { .. } => break,
3891                EventInto::Eof => return Err(self.err("unexpected EOF in particle group")),
3892                _ => {}
3893            }
3894        }
3895
3896        // XSD §3.8.6 cos-all-particle / cos-all-distinct — every
3897        // particle inside an <xs:all> must have a distinct element
3898        // name across the group's lifetime.
3899        if matches!(kind, GroupKind::All) {
3900            let mut seen: HashSet<QName> = HashSet::new();
3901            for p in &particles {
3902                if let Term::Element(decl) = &p.term {
3903                    if !seen.insert(decl.name.clone()) {
3904                        return Err(self.err(format!(
3905                            "<xs:all>: duplicate element {:?} — an all-group's \
3906                             particles must have distinct names (XSD §3.8.6 \
3907                             cos-all-particle)",
3908                            decl.name.local,
3909                        )));
3910                    }
3911                }
3912            }
3913        }
3914
3915        Ok(Particle {
3916            min_occurs,
3917            max_occurs,
3918            term: Term::Group { kind, particles: particles.into() },
3919        })
3920    }
3921
3922    /// Parse a local `<xs:element>` inside a content model.  Builds an
3923    /// inline element decl (anonymous types are scoped to the
3924    /// containing complex type — for v1 we register them in `elements`
3925    /// only when they have a name; truly anonymous locals get a
3926    /// generated synthetic name).
3927    fn parse_local_element_particle(&mut self, attrs: &[Attr<'a>])
3928        -> Result<Particle, SchemaCompileError>
3929    {
3930        let min_occurs = parse_min_occurs(self.attr(attrs, "minOccurs"))?;
3931        let max_occurs = parse_max_occurs(self.attr(attrs, "maxOccurs"))?;
3932        check_occurs(min_occurs, max_occurs)?;
3933        // XSD §3.3.2: `abstract`, `final`, `substitutionGroup`, and
3934        // top-level-only `block` tokens are forbidden on a local
3935        // element declaration (they only apply to the global decl).
3936        // Check these before the general allow-list so the diagnostic
3937        // points the schema author at the precise rule.
3938        for forbidden in ["abstract", "final", "substitutionGroup"] {
3939            if self.attr(attrs, forbidden).is_some() {
3940                return Err(self.err(format!(
3941                    "<xs:element {forbidden}=...> is only valid on top-level element declarations"
3942                )));
3943            }
3944        }
3945        // XSD §3.3.2 — local <xs:element> takes a union of the named
3946        // and ref-form attribute sets; the form-specific exclusions
3947        // are enforced below.
3948        self.check_known_attrs(attrs, &[
3949            "id", "name", "ref", "form", "type", "default", "fixed",
3950            "nillable", "minOccurs", "maxOccurs", "block",
3951        ], "element")?;
3952
3953        // ref="..." form — refers to a top-level element. XSD §3.3.3
3954        // forbids combining `ref` with attributes that customize the
3955        // referenced declaration (`name`, `form`, `type`, `default`,
3956        // `fixed`, `nillable`, `block`); only min/max occurs are
3957        // allowed alongside it.
3958        if let Some(r) = self.attr(attrs, "ref") {
3959            for forbidden in ["name", "form", "type", "default", "fixed", "nillable", "block"] {
3960                if self.attr(attrs, forbidden).is_some() {
3961                    return Err(self.err(format!(
3962                        "<xs:element ref=...> cannot also carry '{forbidden}'"
3963                    )));
3964                }
3965            }
3966            let qn = self.parse_qname(r, false)?;
3967            self.builder.pending_element_refs.push(qn.clone());
3968            // Stand-in decl; the post-pass patches in the real one.
3969            let placeholder = Arc::new(ElementDecl {
3970                name: qn.clone(),
3971                type_def: TypeRef::Simple(Arc::new(SimpleType::of_builtin(BuiltinType::String))),
3972                nillable: false,
3973                default: None,
3974                fixed: None,
3975                abstract_: false,
3976                substitution_group: None,
3977                block:  BlockSet::default(),
3978                final_: BlockSet::default(),
3979                identity: Vec::new(),
3980            });
3981            // XSD §3.3.2 — the only child allowed on a ref-form
3982            // element use is <xs:annotation>.  Inline type bodies,
3983            // identity constraints, etc. are schema validity errors.
3984            self.parse_anno_only_body("element")?;
3985            return Ok(Particle { min_occurs, max_occurs, term: Term::Element(placeholder) });
3986        }
3987
3988        // name="..." form — local declaration.  Namespace is decided
3989        // by `form=` (local override) or `elementFormDefault` on the
3990        // schema document.  Per XSD §3.3.2, qualified locals live in
3991        // the target namespace; unqualified locals live in no
3992        // namespace.
3993        let name = self.attr(attrs, "name").ok_or_else(||
3994            self.err("local <xs:element> needs name or ref")
3995        )?.to_owned();
3996        let form = match self.attr(attrs, "form") {
3997            Some(raw) => Form::parse(raw).ok_or_else(|| self.err(format!(
3998                "<xs:element form={raw:?}>: must be \"qualified\" or \"unqualified\""
3999            )))?,
4000            None => self.element_form_default,
4001        };
4002        // default= and fixed= are mutually exclusive (XSD §3.3.2).
4003        if self.attr(attrs, "default").is_some() && self.attr(attrs, "fixed").is_some() {
4004            return Err(self.err(
4005                "<xs:element> may have either default= or fixed=, not both"
4006            ));
4007        }
4008        let qn = QName {
4009            namespace: match form {
4010                Form::Qualified   => self.current_target_ns.clone(),
4011                Form::Unqualified => None,
4012            },
4013            local: Arc::from(name.as_str()),
4014        };
4015        let (inline, identity) = self.parse_inline_type(attrs)?;
4016        if self.attr(attrs, "type").is_some() && inline.is_some() {
4017            return Err(self.err(
4018                "<xs:element> may have either type= or an inline type body, not both"
4019            ));
4020        }
4021        let type_def = match self.attr(attrs, "type") {
4022            Some(t) => {
4023                let type_qn = self.parse_qname(t, false)?;
4024                self.type_ref_for(type_qn)
4025            }
4026            None => inline.unwrap_or_else(any_type_ref),
4027        };
4028        // Local elements honour their own `block=` (XSD §3.3.2).
4029        // `final=`, `abstract=`, and `substitutionGroup=` are only
4030        // legal on top-level decls — already rejected upstream.
4031        let block = match self.attr(attrs, "block") {
4032            Some(_) => parse_block_set(self.attr(attrs, "block"))?,
4033            None    => self.block_default,
4034        };
4035        let decl = Arc::new(ElementDecl {
4036            name: qn,
4037            type_def,
4038            nillable:  self.parse_xsd_bool(attrs, "nillable")?.unwrap_or(false),
4039            default:   self.attr(attrs, "default").map(|s| s.to_owned()),
4040            fixed:     self.attr(attrs, "fixed").map(|s| s.to_owned()),
4041            abstract_: false,
4042            substitution_group: None,
4043            block,
4044            final_: BlockSet::default(),
4045            identity,
4046        });
4047        Ok(Particle { min_occurs, max_occurs, term: Term::Element(decl) })
4048    }
4049
4050    fn parse_local_attribute_use(&mut self, attrs: &[Attr<'a>])
4051        -> Result<AttributeUse, SchemaCompileError>
4052    {
4053        // XSD §3.2.2 — attribute set of a local <xs:attribute>.
4054        self.check_known_attrs(attrs, &[
4055            "id", "name", "ref", "type", "use", "default", "fixed", "form",
4056        ], "attribute")?;
4057        let use_kind = match self.attr(attrs, "use") {
4058            Some("required")   => AttributeUseKind::Required,
4059            Some("prohibited") => AttributeUseKind::Prohibited,
4060            Some("optional")   => AttributeUseKind::Optional,
4061            None               => AttributeUseKind::Optional,
4062            Some(other) => return Err(self.err(format!(
4063                "<xs:attribute use={other:?}>: must be \"required\", \"optional\", or \"prohibited\""
4064            ))),
4065        };
4066        // XSD §3.2.3 — if `default` is present, `use` must be
4067        // "optional" (the default supplies a value when the attribute
4068        // is missing, which is meaningless for required/prohibited).
4069        if self.attr(attrs, "default").is_some()
4070            && !matches!(use_kind, AttributeUseKind::Optional)
4071        {
4072            return Err(self.err(
4073                "<xs:attribute default=...> requires use=\"optional\"",
4074            ));
4075        }
4076
4077        // ref form.
4078        if let Some(r) = self.attr(attrs, "ref") {
4079            // XSD §3.2.3 — a `ref`-form attribute use cannot carry
4080            // attributes that customize the referenced declaration:
4081            // `name`, `form`, `type`, and an inline simpleType body
4082            // are forbidden alongside `ref`.
4083            for forbidden in ["name", "form", "type"] {
4084                if self.attr(attrs, forbidden).is_some() {
4085                    return Err(self.err(format!(
4086                        "<xs:attribute ref=...> cannot also carry '{forbidden}'"
4087                    )));
4088                }
4089            }
4090            let qn = self.parse_qname(r, false)?;
4091            // XSD §3.2.3 cvc-attribute-use clause 4 — a `ref`-form
4092            // attribute use's `fixed` must match the referenced
4093            // declaration's `fixed` when both are present.  Defer the
4094            // check for forward refs.
4095            let use_fixed = self.attr(attrs, "fixed").map(|s| s.to_owned());
4096            if let Some(use_fixed_v) = use_fixed.as_deref() {
4097                if let Some(decl) = self.builder.attributes.get(&qn) {
4098                    if let Some(decl_fixed) = decl.fixed.as_deref() {
4099                        if decl_fixed != use_fixed_v {
4100                            return Err(self.err(format!(
4101                                "<xs:attribute ref={r:?} fixed={use_fixed_v:?}>: \
4102                                 referenced declaration has fixed={decl_fixed:?}; \
4103                                 a ref-form use cannot change the fixed value"
4104                            )));
4105                        }
4106                    }
4107                }
4108            }
4109            self.builder.pending_attribute_refs.push(qn.clone());
4110            let placeholder = Arc::new(AttributeDecl {
4111                name: qn,
4112                type_def: Arc::new(SimpleType::of_builtin(BuiltinType::String)),
4113                default: self.attr(attrs, "default").map(|s| s.to_owned()),
4114                fixed:   use_fixed,
4115                // ref form: the value of `inheritable` comes from the
4116                // top-level declaration this ref points at, not from
4117                // the use site.  The placeholder gets `false` for now;
4118                // the post-pass that resolves refs replaces the whole
4119                // Arc with the real decl, so this field is overwritten
4120                // before validation runs.
4121                inheritable: false,
4122            });
4123            // The ref form's body may only contain <xs:annotation>.
4124            self.parse_anno_only_body("attribute")?;
4125            return Ok(AttributeUse {
4126                use_kind,
4127                decl: placeholder,
4128                default: None,
4129                fixed: None,
4130            });
4131        }
4132
4133        let name = self.attr(attrs, "name").ok_or_else(||
4134            self.err("local <xs:attribute> needs name or ref")
4135        )?.to_owned();
4136        if name == "xmlns" {
4137            return Err(self.err(
4138                "<xs:attribute name=\"xmlns\"> is reserved by the Namespaces in XML spec",
4139            ));
4140        }
4141        let form = match self.attr(attrs, "form") {
4142            Some(raw) => Form::parse(raw).ok_or_else(|| self.err(format!(
4143                "<xs:attribute form={raw:?}>: must be \"qualified\" or \"unqualified\""
4144            )))?,
4145            None => self.attribute_form_default,
4146        };
4147        if self.attr(attrs, "default").is_some() && self.attr(attrs, "fixed").is_some() {
4148            return Err(self.err(
4149                "<xs:attribute> may have either default= or fixed=, not both"
4150            ));
4151        }
4152        let qn = QName {
4153            namespace: match form {
4154                Form::Qualified   => self.current_target_ns.clone(),
4155                Form::Unqualified => None,
4156            },
4157            local: Arc::from(name.as_str()),
4158        };
4159        check_xsi_attribute_name(&qn).map_err(|m| self.err(m))?;
4160        let inline = self.parse_inline_simple_type(attrs)?;
4161        if self.attr(attrs, "type").is_some() && inline.is_some() {
4162            return Err(self.err(
4163                "<xs:attribute> may have either type= or an inline <xs:simpleType>, not both"
4164            ));
4165        }
4166        let st = match self.attr(attrs, "type") {
4167            Some(t) => {
4168                let type_qn = self.parse_qname(t, false)?;
4169                self.simple_type_for(type_qn)
4170            }
4171            None => Arc::new(inline.unwrap_or_else(|| SimpleType::of_builtin(BuiltinType::String))),
4172        };
4173        let decl = Arc::new(AttributeDecl {
4174            name: qn,
4175            type_def: st,
4176            default: self.attr(attrs, "default").map(|s| s.to_owned()),
4177            fixed:   self.attr(attrs, "fixed").map(|s| s.to_owned()),
4178            inheritable: self.parse_inheritable(attrs)?,
4179        });
4180        Ok(AttributeUse { use_kind, decl, default: None, fixed: None })
4181    }
4182
4183    fn parse_top_attribute_group(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
4184        // XSD §3.6.2: a top-level <xs:attributeGroup> requires `name`,
4185        // its body is (annotation?, ((attribute|attributeGroup)*, anyAttribute?)),
4186        // and inner <xs:attributeGroup> must be a ref (no nested defs).
4187        let name = match self.attr(attrs, "name") {
4188            Some(n) => QName {
4189                namespace: self.current_target_ns.clone(),
4190                local:     Arc::from(n),
4191            },
4192            None => return Err(self.err(
4193                "top-level <xs:attributeGroup> requires a 'name' attribute",
4194            )),
4195        };
4196        if !self.in_redefine && self.builder.attr_groups.contains_key(&name) {
4197            return Err(self.err(format!(
4198                "duplicate <xs:attributeGroup name={:?}> in target namespace",
4199                name.local,
4200            )));
4201        }
4202        let mut uses = Vec::new();
4203        let mut any  = None;
4204        let mut local_refs: Vec<QName> = Vec::new();
4205        let mut seen_anno = false;
4206        let mut seen_other = false;
4207        let mut seen_any_attribute = false;
4208        loop {
4209            match self.next_event()? {
4210                EventInto::StartElement { name: child } => {
4211                    let cattrs = self.take_attrs()?;
4212                    self.push_ns_scope(&cattrs);
4213                    let qn = self.qname_of_element(&child)?;
4214                    if qn.namespace.as_deref() == Some(XS) {
4215                        self.check_annotation_pos(
4216                            &qn.local, &mut seen_anno, &mut seen_other, "attributeGroup",
4217                        )?;
4218                        match qn.local.as_ref() {
4219                            "attribute" => {
4220                                if seen_any_attribute {
4221                                    return Err(self.err(
4222                                        "<xs:attribute> cannot follow <xs:anyAttribute> in <xs:attributeGroup>",
4223                                    ));
4224                                }
4225                                uses.push(self.parse_local_attribute_use(&cattrs)?);
4226                            }
4227                            "attributeGroup" => {
4228                                if seen_any_attribute {
4229                                    return Err(self.err(
4230                                        "<xs:attributeGroup> cannot follow <xs:anyAttribute> in <xs:attributeGroup>",
4231                                    ));
4232                                }
4233                                let r = self.attr(&cattrs, "ref").ok_or_else(|| self.err(
4234                                    "<xs:attributeGroup> inside another attributeGroup must use ref= \
4235                                     (nested definitions are not allowed)",
4236                                ))?;
4237                                let rqn = self.parse_qname(r, false)?;
4238                                self.builder.pending_ag_refs_in_ag.push(rqn.clone());
4239                                local_refs.push(rqn.clone());
4240                                if Some(&rqn) == self.builder.redefining_ag_name.as_ref() {
4241                                    self.builder.redefining_ag_saw_self_ref = true;
4242                                }
4243                                if let Some(ag) = self.builder.attr_groups.get(&rqn) {
4244                                    for au in &ag.attributes {
4245                                        uses.push(au.clone());
4246                                    }
4247                                    any = merge_any_intersect(any, ag.any.clone());
4248                                }
4249                                self.parse_anno_only_body("attributeGroup")?;
4250                            }
4251                            "anyAttribute" => {
4252                                if seen_any_attribute {
4253                                    return Err(self.err(
4254                                        "<xs:attributeGroup> body: at most one <xs:anyAttribute>",
4255                                    ));
4256                                }
4257                                seen_any_attribute = true;
4258                                check_no_occurs(&cattrs, "anyAttribute")?;
4259                                any = Some(self.parse_wildcard(&cattrs)?);
4260                                self.parse_anno_only_body("anyAttribute")?;
4261                            }
4262                            "annotation" => self.parse_annotation_body(&cattrs)?,
4263                            other => return Err(self.err(format!(
4264                                "<xs:{other}> is not allowed as a child of <xs:attributeGroup>"
4265                            ))),
4266                        }
4267                    } else { self.skip_body()?; }
4268                    self.pop_ns_scope();
4269                }
4270                EventInto::EndElement { .. } => break,
4271                EventInto::Eof => return Err(self.err("unexpected EOF in attributeGroup")),
4272                _ => {}
4273            }
4274        }
4275        // Inside <xs:redefine>, a self-reference means "include the
4276        // pre-redefinition content" (XSD §4.2.2) — semantically NOT a
4277        // cyclic reference.  Skip cycle bookkeeping for redefined
4278        // groups so the legitimate self-include doesn't trip
4279        // src-attribute-group-cyclic.
4280        if !self.in_redefine {
4281            self.builder.ag_refs_by_owner.insert(name.clone(), local_refs);
4282        }
4283        self.builder.attr_groups.insert(name.clone(), Arc::new(AttributeGroup {
4284            name, attributes: uses, any,
4285        }));
4286        Ok(())
4287    }
4288
4289    fn parse_top_group(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
4290        // XSD §3.7.1 — a top-level <xs:group> requires `name`
4291        // (no `ref=` is allowed at the schema top level), and its
4292        // body contains exactly one sequence | choice | all
4293        // model-group child (plus an optional leading annotation).
4294        if self.attr(attrs, "ref").is_some() {
4295            return Err(self.err(
4296                "<xs:group ref=...> is only valid inside a complexType / model-group, \
4297                 not at schema top level",
4298            ));
4299        }
4300        // XSD §3.7.2 — top-level <xs:group> does not take minOccurs
4301        // or maxOccurs; those apply only at reference sites.
4302        for forbidden in ["minOccurs", "maxOccurs"] {
4303            if self.attr(attrs, forbidden).is_some() {
4304                return Err(self.err(format!(
4305                    "top-level <xs:group> does not take '{forbidden}'"
4306                )));
4307            }
4308        }
4309        let name = match self.attr(attrs, "name") {
4310            Some(n) => QName {
4311                namespace: self.current_target_ns.clone(),
4312                local:     Arc::from(n),
4313            },
4314            None => return Err(self.err(
4315                "top-level <xs:group> requires a 'name' attribute",
4316            )),
4317        };
4318        if !self.in_redefine && self.builder.model_groups.contains_key(&name) {
4319            return Err(self.err(format!(
4320                "duplicate <xs:group name={:?}> in target namespace",
4321                name.local,
4322            )));
4323        }
4324        let mut particle: Option<Particle> = None;
4325        let mut seen_anno = false;
4326        let mut seen_other = false;
4327        loop {
4328            match self.next_event()? {
4329                EventInto::StartElement { name: child } => {
4330                    let cattrs = self.take_attrs()?;
4331                    self.push_ns_scope(&cattrs);
4332                    let qn = self.qname_of_element(&child)?;
4333                    if qn.namespace.as_deref() == Some(XS) {
4334                        self.check_annotation_pos(
4335                            &qn.local, &mut seen_anno, &mut seen_other, "group",
4336                        )?;
4337                        match qn.local.as_ref() {
4338                            "sequence" | "choice" | "all" => {
4339                                if particle.is_some() {
4340                                    return Err(self.err(
4341                                        "<xs:group> must contain exactly one of \
4342                                         <xs:sequence>, <xs:choice>, or <xs:all>",
4343                                    ));
4344                                }
4345                                let kind = match qn.local.as_ref() {
4346                                    "sequence" => GroupKind::Sequence,
4347                                    "choice"   => GroupKind::Choice,
4348                                    _          => GroupKind::All,
4349                                };
4350                                particle = Some(self.parse_group_body(kind, &cattrs)?);
4351                            }
4352                            "annotation" => self.parse_annotation_body(&cattrs)?,
4353                            other => return Err(self.err(format!(
4354                                "<xs:{other}> is not allowed as a child of <xs:group>"
4355                            ))),
4356                        }
4357                    } else { self.skip_body()?; }
4358                    self.pop_ns_scope();
4359                }
4360                EventInto::EndElement { .. } => break,
4361                EventInto::Eof => return Err(self.err("unexpected EOF in group")),
4362                _ => {}
4363            }
4364        }
4365        if let Some(p) = particle {
4366            self.builder.model_groups.insert(name.clone(), Arc::new(ModelGroup { name, particle: p }));
4367        }
4368        Ok(())
4369    }
4370
4371    fn parse_top_notation(&mut self, attrs: &[Attr<'a>]) -> Result<(), SchemaCompileError> {
4372        // XSD §3.12.1 / §3.12.3 — <xs:notation> requires `name`
4373        // (an NCName) and at least one of `public` / `system`.
4374        self.check_known_attrs(attrs, &["id", "name", "public", "system"], "notation")?;
4375        let name_raw = self.attr(attrs, "name")
4376            .ok_or_else(|| self.err("<xs:notation> requires a 'name' attribute"))?;
4377        super::types::SimpleType::of_builtin(super::types::BuiltinType::NCName)
4378            .validate(name_raw)
4379            .map_err(|e| self.err(format!(
4380                "<xs:notation name={name_raw:?}> must be an NCName: {}", e.message,
4381            )))?;
4382        let public_id = self.attr(attrs, "public").map(|s| s.to_owned());
4383        let system_id = self.attr(attrs, "system").map(|s| s.to_owned());
4384        if public_id.is_none() && system_id.is_none() {
4385            return Err(self.err(
4386                "<xs:notation> requires at least one of 'public' or 'system'"
4387            ));
4388        }
4389        let name = QName {
4390            namespace: self.current_target_ns.clone(),
4391            local:     Arc::from(name_raw),
4392        };
4393        if !self.in_redefine && self.builder.notations.contains_key(&name) {
4394            return Err(self.err(format!(
4395                "duplicate <xs:notation name={name_raw:?}> in target namespace"
4396            )));
4397        }
4398        self.parse_anno_only_body("notation")?;
4399        self.builder.notations.insert(name.clone(), Arc::new(NotationDecl {
4400            name, public_id, system_id,
4401        }));
4402        Ok(())
4403    }
4404
4405}
4406
4407// ── helpers ──────────────────────────────────────────────────────────────────
4408
4409/// XSD §3.4.2 — merge each named complex type that derives by
4410/// extension with its base's content model and attribute uses.
4411///
4412/// Resolves the chain bottom-up so transitive extensions
4413/// (`A → B → C`) compose correctly.  The result for each derived
4414/// type:
4415///
4416/// * **Content** — `Sequence[base.content, derived.content]`.  Both
4417///   sides keep their original mixed flag (merged via OR).  When the
4418///   base has `Empty` content the derived's content is used as-is.
4419///   `Simple` base content extension is a different beast (the derived
4420///   stays simple-content and only adds attributes) — handled here
4421///   by passing the base's `Simple(_)` through unchanged.
4422/// * **Attributes** — concatenation; a derived attribute with the
4423///   same name as a base attribute overrides the base (covers
4424///   `use="prohibited"` overrides and explicit redeclaration).
4425/// * **anyAttribute** — derived's overrides base's if present.
4426///
4427/// Cycles in the chain (`A extends B extends A`) are rejected as
4428/// schema-compile errors.  Restriction-by-restriction does NOT
4429/// trigger this merge — restricted types are expected to declare
4430/// their own content model explicitly per spec.
4431///
4432/// Anonymous complex types inlined inside element decls are not
4433/// merged — they can't be referenced by name, so the typical
4434/// `Base`/`Derived` named-type pattern is unaffected.
4435/// Replace `UNRESOLVED:` placeholder member / item types inside union and
4436/// list simple types with the real type from the type map.  Iterated to a
4437/// small fixpoint so chains of unions-of-unions resolve fully.
4438fn resolve_simple_member_placeholders(types: &mut std::collections::HashMap<QName, TypeRef>) {
4439    use super::types::Variety;
4440    let resolve = |m: &Arc<super::types::SimpleType>,
4441                   snap: &std::collections::HashMap<QName, TypeRef>|
4442        -> Arc<super::types::SimpleType> {
4443        if let Some(qn) = resolve_typeref_to_qname(&TypeRef::Simple(m.clone())) {
4444            if let Some(TypeRef::Simple(real)) = snap.get(&qn) {
4445                return real.clone();
4446            }
4447        }
4448        m.clone()
4449    };
4450    for _ in 0..8 {
4451        let snapshot = types.clone();
4452        let mut changed = false;
4453        for tr in types.values_mut() {
4454            let TypeRef::Simple(st) = tr else { continue };
4455            let new_variety = match &st.variety {
4456                Variety::Union { members } => Variety::Union {
4457                    members: members.iter().map(|m| resolve(m, &snapshot)).collect(),
4458                },
4459                Variety::List { item_type } => Variety::List {
4460                    item_type: resolve(item_type, &snapshot),
4461                },
4462                Variety::Atomic => continue,
4463            };
4464            let mut new_st = (**st).clone();
4465            new_st.variety = new_variety;
4466            *st = Arc::new(new_st);
4467            changed = true;
4468        }
4469        if !changed { break; }
4470    }
4471}
4472
4473fn merge_extension_chains(
4474    types: &mut HashMap<QName, TypeRef>,
4475) -> Result<(), SchemaCompileError> {
4476    let names: Vec<QName> = types.keys().cloned().collect();
4477    let mut merged:  HashSet<QName> = HashSet::new();
4478    let mut merging: HashSet<QName> = HashSet::new();
4479    for name in &names {
4480        merge_one_extension(name, types, &mut merged, &mut merging)?;
4481    }
4482    Ok(())
4483}
4484
4485fn merge_one_extension(
4486    name:    &QName,
4487    types:   &mut HashMap<QName, TypeRef>,
4488    merged:  &mut HashSet<QName>,
4489    merging: &mut HashSet<QName>,
4490) -> Result<(), SchemaCompileError> {
4491    if merged.contains(name) { return Ok(()); }
4492    if !merging.insert(name.clone()) {
4493        return Err(SchemaCompileError::msg(format!(
4494            "cyclic complex-type derivation involving {}", name
4495        )));
4496    }
4497
4498    // Snapshot the current entry — we may replace it below.
4499    let current = types.get(name).cloned();
4500    if let Some(TypeRef::Complex(ct)) = current {
4501        if let Some(d) = &ct.derivation {
4502            if d.method == DerivationMethod::Extension {
4503                // Three shapes for the base:
4504                //   * `UNRESOLVED:` Simple placeholder → name lookup
4505                //     (recurse so the chain is merged bottom-up).
4506                //   * `Complex(Arc<…>)` direct reference → composed
4507                //     against that Arc as-is (used by `<xs:redefine>`
4508                //     where the base IS the pre-redefinition original).
4509                //   * Anything else (simple base for simple-content
4510                //     extension, or a built-in like xs:anyType) →
4511                //     leave the derived type unchanged.
4512                let base_ct: Option<Arc<ComplexType>> = match &d.base {
4513                    TypeRef::Complex(c) => Some(c.clone()),
4514                    _ => {
4515                        if let Some(base_qn) = resolve_typeref_to_qname(&d.base) {
4516                            merge_one_extension(&base_qn, types, merged, merging)?;
4517                            match types.get(&base_qn) {
4518                                Some(TypeRef::Complex(c)) => Some(c.clone()),
4519                                _ => None,
4520                            }
4521                        } else { None }
4522                    }
4523                };
4524                if let Some(base_ct) = base_ct {
4525                    let new_ct = compose_extension(&base_ct, &ct);
4526                    types.insert(name.clone(), TypeRef::Complex(Arc::new(new_ct)));
4527                } else if matches!(ct.content, ContentModel::Simple(_)) {
4528                    // simpleContent extension of a simple base type
4529                    // (built-in or user simple type).  The derived's
4530                    // content was initialised to Simple(String) by the
4531                    // parser as a placeholder; replace it with the
4532                    // base's actual simple type so default/fixed
4533                    // validation and instance text validation see the
4534                    // declared type.
4535                    let base_simple: Option<Arc<SimpleType>> = match &d.base {
4536                        TypeRef::Simple(st) if st.name.as_deref()
4537                            .map(|n| !n.starts_with("UNRESOLVED:"))
4538                            .unwrap_or(true) => Some(st.clone()),
4539                        TypeRef::Simple(_) => {
4540                            resolve_typeref_to_qname(&d.base)
4541                                .and_then(|qn| match types.get(&qn) {
4542                                    Some(TypeRef::Simple(s)) => Some(s.clone()),
4543                                    _ => None,
4544                                })
4545                        }
4546                        TypeRef::Complex(_) => None,
4547                    };
4548                    if let Some(base_simple) = base_simple {
4549                        let new_ct = ComplexType {
4550                            name:          ct.name.clone(),
4551                            derivation:    ct.derivation.clone(),
4552                            content:       ContentModel::Simple(base_simple),
4553                            matcher:       std::sync::OnceLock::new(),
4554                            attributes:    ct.attributes.clone(),
4555                            any_attribute: ct.any_attribute.clone(),
4556                            abstract_:     ct.abstract_,
4557                            block:         ct.block,
4558                            final_:        ct.final_,
4559                            pending_attribute_group_refs: ct.pending_attribute_group_refs.clone(),
4560                            assertions: ct.assertions.clone(),
4561                        };
4562                        types.insert(name.clone(), TypeRef::Complex(Arc::new(new_ct)));
4563                    }
4564                }
4565            }
4566        }
4567    }
4568
4569    merged.insert(name.clone());
4570    merging.remove(name);
4571    Ok(())
4572}
4573
4574/// Apply `compose_extension` to inline anonymous complex types that
4575/// hang off element decls.  Top-level named types are handled by
4576/// [`merge_extension_chains`]; this fills the gap for declarations
4577/// like `<xs:element name="Foo"><xs:complexType><xs:complexContent>
4578/// <xs:extension base="Bar">…`, whose `Bar` lookup is satisfied by
4579/// the already-merged `types` map.
4580///
4581/// Element decls are stored as `Arc<ElementDecl>`, shared with
4582/// `resolve_element_refs`'s patched content models.  We rebuild the
4583/// Arc when the inline type changes so the substitution map (built
4584/// just after this pass) captures the merged version.
4585/// Fold each restriction-derived complex type's base attribute set
4586/// onto the derived type, keeping only those base attribute uses
4587/// that the derived type doesn't already redeclare.  XSD §3.4.2
4588/// treats unmentioned base attribute uses as implicitly inherited;
4589/// without this pass the derived would silently drop them.
4590fn merge_restriction_attributes(
4591    types:    &mut HashMap<QName, TypeRef>,
4592    elements: &mut HashMap<QName, Arc<ElementDecl>>,
4593) {
4594    use super::types::DerivationMethod;
4595
4596    fn resolved_base<'a>(
4597        d: &super::types::Derivation,
4598        types: &'a HashMap<QName, TypeRef>,
4599    ) -> Option<Arc<ComplexType>> {
4600        match &d.base {
4601            TypeRef::Complex(c) => Some(c.clone()),
4602            _ => resolve_typeref_to_qname(&d.base)
4603                .and_then(|qn| match types.get(&qn) {
4604                    Some(TypeRef::Complex(c)) => Some(c.clone()),
4605                    _ => None,
4606                }),
4607        }
4608    }
4609
4610    fn merge_one(ct: &ComplexType, base: &ComplexType) -> ComplexType {
4611        use super::schema::{AttributeUseKind, AttributeUse};
4612        let mut merged: Vec<AttributeUse> = ct.attributes.clone();
4613        for b_au in &base.attributes {
4614            // A redeclaration (same name) wins by virtue of being
4615            // already present; prohibited uses in the derived stay
4616            // prohibited; missing base uses get carried over unless
4617            // they were `prohibited` in the base (in which case
4618            // there's nothing to inherit).
4619            if merged.iter().any(|a| a.decl.name == b_au.decl.name) {
4620                continue;
4621            }
4622            if b_au.use_kind == AttributeUseKind::Prohibited {
4623                continue;
4624            }
4625            merged.push(b_au.clone());
4626        }
4627        // anyAttribute on the derived overrides the base's wildcard;
4628        // if absent, inherit the base's.
4629        let any_attribute = ct.any_attribute.clone().or_else(|| base.any_attribute.clone());
4630        ComplexType {
4631            name:          ct.name.clone(),
4632            derivation:    ct.derivation.clone(),
4633            content:       ct.content.clone(),
4634            matcher:       std::sync::OnceLock::new(),
4635            attributes:    merged,
4636            any_attribute,
4637            abstract_:     ct.abstract_,
4638            block:         ct.block,
4639            final_:        ct.final_,
4640            pending_attribute_group_refs: ct.pending_attribute_group_refs.clone(),
4641            assertions: ct.assertions.clone(),
4642        }
4643    }
4644
4645    let names: Vec<QName> = types.keys().cloned().collect();
4646    for name in names {
4647        let Some(TypeRef::Complex(ct)) = types.get(&name).cloned() else { continue };
4648        let Some(d) = &ct.derivation else { continue };
4649        if d.method != DerivationMethod::Restriction { continue; }
4650        let Some(base) = resolved_base(d, types) else { continue };
4651        let merged = merge_one(&ct, &base);
4652        types.insert(name, TypeRef::Complex(Arc::new(merged)));
4653    }
4654
4655    // Same treatment for inline anonymous types attached to top-level
4656    // element decls.
4657    let elem_names: Vec<QName> = elements.keys().cloned().collect();
4658    for ename in elem_names {
4659        let Some(decl) = elements.get(&ename).cloned() else { continue };
4660        let TypeRef::Complex(ct) = &decl.type_def else { continue };
4661        if ct.name.is_some() { continue; }
4662        let Some(d) = &ct.derivation else { continue };
4663        if d.method != DerivationMethod::Restriction { continue; }
4664        let Some(base) = resolved_base(d, types) else { continue };
4665        let merged = merge_one(ct, &base);
4666        let new_decl = ElementDecl {
4667            name:               decl.name.clone(),
4668            type_def:           TypeRef::Complex(Arc::new(merged)),
4669            nillable:           decl.nillable,
4670            default:            decl.default.clone(),
4671            fixed:              decl.fixed.clone(),
4672            abstract_:          decl.abstract_,
4673            substitution_group: decl.substitution_group.clone(),
4674            identity:           decl.identity.clone(),
4675            block:              decl.block,
4676            final_:             decl.final_,
4677        };
4678        elements.insert(ename, Arc::new(new_decl));
4679    }
4680}
4681
4682fn merge_inline_extension_in_elements(
4683    elements: &mut HashMap<QName, Arc<ElementDecl>>,
4684    types:    &HashMap<QName, TypeRef>,
4685) {
4686    let names: Vec<QName> = elements.keys().cloned().collect();
4687    for name in names {
4688        let Some(decl) = elements.get(&name).cloned() else { continue };
4689        let TypeRef::Complex(ct) = &decl.type_def else { continue };
4690        // Skip named types — they're handled by merge_extension_chains.
4691        if ct.name.is_some() { continue; }
4692        let Some(d) = &ct.derivation else { continue };
4693        if d.method != DerivationMethod::Extension { continue; }
4694        let base_ct: Option<Arc<ComplexType>> = match &d.base {
4695            TypeRef::Complex(c) => Some(c.clone()),
4696            _ => resolve_typeref_to_qname(&d.base)
4697                .and_then(|qn| types.get(&qn).cloned())
4698                .and_then(|tr| match tr {
4699                    TypeRef::Complex(c) => Some(c),
4700                    _ => None,
4701                }),
4702        };
4703        let merged = if let Some(base_ct) = base_ct {
4704            compose_extension(&base_ct, ct)
4705        } else if matches!(ct.content, ContentModel::Simple(_)) {
4706            // simpleContent extension whose base is a simple type
4707            // (built-in or user simple).  parse_derivation_body left
4708            // the carrier's content as `Simple(string)` as a
4709            // placeholder; substitute the real base so facet checks
4710            // on element text see the declared type.
4711            let base_simple: Option<Arc<SimpleType>> = match &d.base {
4712                TypeRef::Simple(st) if st.name.as_deref()
4713                    .map(|n| !n.starts_with("UNRESOLVED:"))
4714                    .unwrap_or(true) => Some(st.clone()),
4715                TypeRef::Simple(_) => resolve_typeref_to_qname(&d.base)
4716                    .and_then(|qn| match types.get(&qn) {
4717                        Some(TypeRef::Simple(s)) => Some(s.clone()),
4718                        _ => None,
4719                    }),
4720                TypeRef::Complex(_) => None,
4721            };
4722            let Some(base_simple) = base_simple else { continue };
4723            ComplexType {
4724                name:          ct.name.clone(),
4725                derivation:    ct.derivation.clone(),
4726                content:       ContentModel::Simple(base_simple),
4727                matcher:       std::sync::OnceLock::new(),
4728                attributes:    ct.attributes.clone(),
4729                any_attribute: ct.any_attribute.clone(),
4730                final_:        ct.final_,
4731                block:         ct.block,
4732                abstract_:     ct.abstract_,
4733                pending_attribute_group_refs: ct.pending_attribute_group_refs.clone(),
4734                assertions: ct.assertions.clone(),
4735            }
4736        } else { continue };
4737        let new_decl = ElementDecl {
4738            name:                decl.name.clone(),
4739            type_def:            TypeRef::Complex(Arc::new(merged)),
4740            nillable:            decl.nillable,
4741            default:             decl.default.clone(),
4742            fixed:               decl.fixed.clone(),
4743            abstract_:           decl.abstract_,
4744            substitution_group:  decl.substitution_group.clone(),
4745            identity:            decl.identity.clone(),
4746            block:               decl.block,
4747            final_:              decl.final_,
4748        };
4749        elements.insert(name, Arc::new(new_decl));
4750    }
4751}
4752
4753/// Pull a [`QName`] out of a TypeRef when it's an `UNRESOLVED:` Simple
4754/// placeholder produced by `Builder::type_ref_for`.  Returns `None`
4755/// for built-in types or for placeholders whose marker can't be parsed.
4756fn check_derivation_content_kind(
4757    types: &HashMap<QName, TypeRef>,
4758) -> Result<(), SchemaCompileError> {
4759    for (name, tr) in types {
4760        let TypeRef::Complex(ct) = tr else { continue };
4761        let Some(deriv) = ct.derivation.as_ref() else { continue };
4762        // XSD §3.4.2 — simpleContent restriction's base MUST be a
4763        // complex type (with simple content), never a simple type
4764        // directly. The extension form (`<xs:extension>`) does
4765        // allow a simple-type base.
4766        let derived_is_simple = matches!(ct.content, ContentModel::Simple(_));
4767        let derived_is_complex = matches!(ct.content, ContentModel::Complex { .. });
4768        if derived_is_simple && deriv.method == DerivationMethod::Restriction {
4769            let base_is_complex = match &deriv.base {
4770                TypeRef::Complex(_) => true,
4771                TypeRef::Simple(_)  => match resolve_typeref_to_qname(&deriv.base) {
4772                    Some(qn) => matches!(types.get(&qn), Some(TypeRef::Complex(_))),
4773                    None => false, // built-in xs:* — always Simple
4774                },
4775            };
4776            if !base_is_complex {
4777                return Err(SchemaCompileError::msg(format!(
4778                    "<xs:complexType name={:?}>: <xs:simpleContent><xs:restriction> \
4779                     base must be a complex type with simple content, not a simple type",
4780                    name.local,
4781                )));
4782            }
4783        }
4784        // Resolve the base — placeholder Simple or direct Complex.
4785        let base_ct = match &deriv.base {
4786            TypeRef::Complex(c) => Some(c.clone()),
4787            TypeRef::Simple(_)  => match resolve_typeref_to_qname(&deriv.base) {
4788                Some(qn) => match types.get(&qn) {
4789                    Some(TypeRef::Complex(c)) => Some(c.clone()),
4790                    _ => None,
4791                },
4792                None => None,
4793            },
4794        };
4795        // XSD §3.4.2 — complexContent extension/restriction's base
4796        // must itself be a complex type.  A simple-type base (whether
4797        // a built-in like xs:string or a named user simpleType) is a
4798        // schema validity error.
4799        if derived_is_complex {
4800            let base_is_simple = match &deriv.base {
4801                TypeRef::Simple(st) => match resolve_typeref_to_qname(&deriv.base) {
4802                    Some(qn) if qn.namespace.as_deref() == Some(QName::XSD_NS) => {
4803                        // Built-in simple types resolve via BuiltinType.
4804                        // xs:anyType (which is complex) is the only
4805                        // XSD-namespace name that isn't a simple type.
4806                        BuiltinType::from_name(&qn.local).is_some()
4807                    }
4808                    Some(qn) => matches!(types.get(&qn), Some(TypeRef::Simple(_))),
4809                    // No UNRESOLVED marker — must be a resolved built-in
4810                    // (`type_ref_for` strips builtins straight to a
4811                    // SimpleType with name=None).
4812                    None => st.name.is_none(),
4813                },
4814                _ => false,
4815            };
4816            if base_is_simple {
4817                return Err(SchemaCompileError::msg(format!(
4818                    "<xs:complexType name={:?}>: <xs:complexContent> base must be a \
4819                     complex type, not a simple type",
4820                    name.local,
4821                )));
4822            }
4823        }
4824        let Some(base_ct) = base_ct else { continue };
4825        let base_is_simple = matches!(base_ct.content, ContentModel::Simple(_));
4826        let base_is_complex = matches!(base_ct.content, ContentModel::Complex { .. });
4827        if derived_is_complex && base_is_simple {
4828            return Err(SchemaCompileError::msg(format!(
4829                "<xs:complexType name={:?}>: complexContent derivation cannot \
4830                 have a simpleContent base",
4831                name.local,
4832            )));
4833        }
4834        if derived_is_simple && base_is_complex {
4835            return Err(SchemaCompileError::msg(format!(
4836                "<xs:complexType name={:?}>: simpleContent derivation cannot \
4837                 have a complexContent base",
4838                name.local,
4839            )));
4840        }
4841    }
4842    Ok(())
4843}
4844
4845fn check_complex_type_final(
4846    types:    &HashMap<QName, TypeRef>,
4847    elements: &HashMap<QName, Arc<ElementDecl>>,
4848) -> Result<(), SchemaCompileError> {
4849    use super::types::DerivationMethod;
4850    fn check_one(
4851        ct: &ComplexType,
4852        types: &HashMap<QName, TypeRef>,
4853        label: &str,
4854    ) -> Result<(), SchemaCompileError> {
4855        let Some(d) = &ct.derivation else { return Ok(()) };
4856        let base_ct: Option<Arc<ComplexType>> = match &d.base {
4857            TypeRef::Complex(c) => Some(c.clone()),
4858            TypeRef::Simple(_) => resolve_typeref_to_qname(&d.base)
4859                .and_then(|qn| match types.get(&qn) {
4860                    Some(TypeRef::Complex(c)) => Some(c.clone()),
4861                    _ => None,
4862                }),
4863        };
4864        let Some(base_ct) = base_ct else { return Ok(()) };
4865        let blocked = match d.method {
4866            DerivationMethod::Restriction => base_ct.final_.contains(BlockSet::RESTRICTION),
4867            DerivationMethod::Extension   => base_ct.final_.contains(BlockSet::EXTENSION),
4868        };
4869        if blocked {
4870            return Err(SchemaCompileError::msg(format!(
4871                "{label}: base complex type's `final` disallows {} derivation",
4872                match d.method {
4873                    DerivationMethod::Restriction => "restriction",
4874                    DerivationMethod::Extension   => "extension",
4875                },
4876            )));
4877        }
4878        Ok(())
4879    }
4880    for (name, tr) in types {
4881        if let TypeRef::Complex(ct) = tr {
4882            let label = format!("<xs:complexType name={:?}>", name.local);
4883            check_one(ct, types, &label)?;
4884        }
4885    }
4886    for (name, decl) in elements {
4887        if let TypeRef::Complex(ct) = &decl.type_def {
4888            let label = format!("<xs:element name={:?}>'s inline type", name.local);
4889            check_one(ct, types, &label)?;
4890        }
4891    }
4892    Ok(())
4893}
4894
4895fn check_complex_mixed_consistency(
4896    types:    &HashMap<QName, TypeRef>,
4897    elements: &HashMap<QName, Arc<ElementDecl>>,
4898) -> Result<(), SchemaCompileError> {
4899    fn mixed_of(c: &ContentModel) -> Option<bool> {
4900        match c {
4901            ContentModel::Complex { mixed, .. } => Some(*mixed),
4902            _ => None,
4903        }
4904    }
4905    fn check_one(
4906        ct: &ComplexType,
4907        types: &HashMap<QName, TypeRef>,
4908        label: &str,
4909    ) -> Result<(), SchemaCompileError> {
4910        let Some(d) = &ct.derivation else { return Ok(()) };
4911        let Some(d_mixed) = mixed_of(&ct.content) else { return Ok(()) };
4912        let base_ct: Option<Arc<ComplexType>> = match &d.base {
4913            TypeRef::Complex(c) => Some(c.clone()),
4914            TypeRef::Simple(_) => resolve_typeref_to_qname(&d.base)
4915                .and_then(|qn| match types.get(&qn) {
4916                    Some(TypeRef::Complex(c)) => Some(c.clone()),
4917                    _ => None,
4918                }),
4919        };
4920        let Some(base_ct) = base_ct else { return Ok(()) };
4921        let Some(b_mixed) = mixed_of(&base_ct.content) else { return Ok(()) };
4922        let bad = match d.method {
4923            // XSD §3.4.6 cos-ct-extends: mixed must match exactly.
4924            super::types::DerivationMethod::Extension => b_mixed != d_mixed,
4925            // cos-derived-ok-restriction-3 lets restriction *tighten*
4926            // mixed→element-only, but not the reverse.
4927            super::types::DerivationMethod::Restriction => !b_mixed && d_mixed,
4928        };
4929        if bad {
4930            return Err(SchemaCompileError::msg(format!(
4931                "{label}: complexContent {} cannot change mixed from {b_mixed} to {d_mixed}",
4932                match d.method {
4933                    super::types::DerivationMethod::Extension => "extension",
4934                    super::types::DerivationMethod::Restriction => "restriction",
4935                },
4936            )));
4937        }
4938        Ok(())
4939    }
4940    for (name, tr) in types {
4941        if let TypeRef::Complex(ct) = tr {
4942            let label = format!("<xs:complexType name={:?}>", name.local);
4943            check_one(ct, types, &label)?;
4944        }
4945    }
4946    for (name, decl) in elements {
4947        if let TypeRef::Complex(ct) = &decl.type_def {
4948            let label = format!("<xs:element name={:?}>'s inline type", name.local);
4949            check_one(ct, types, &label)?;
4950        }
4951    }
4952    Ok(())
4953}
4954
4955fn ns_constraint_subset(
4956    derived: &super::schema::NamespaceConstraint,
4957    base:    &super::schema::NamespaceConstraint,
4958) -> bool {
4959    use super::schema::NamespaceConstraint::*;
4960    match (derived, base) {
4961        (_,             Any)   => true,
4962        (Any,           _)     => false,
4963        (Other,         Other) => true,
4964        (Other,         _)     => false,
4965        (List(d),       Other) => d.iter().all(|e| e.is_some()),
4966        (List(d),       List(b)) => d.iter().all(|d|
4967            b.iter().any(|b| match (d, b) {
4968                (None, None) => true,
4969                (Some(x), Some(y)) => x == y,
4970                _ => false,
4971            })
4972        ),
4973    }
4974}
4975
4976fn wildcard_allows_ns(
4977    c:  &super::schema::NamespaceConstraint,
4978    ns: Option<&str>,
4979) -> bool {
4980    use super::schema::NamespaceConstraint::*;
4981    match c {
4982        Any => true,
4983        Other => ns.is_some(),
4984        List(entries) => entries.iter().any(|e| match (e, ns) {
4985            (None, None)            => true,
4986            (Some(u), Some(n))      => u.as_ref() == n,
4987            _ => false,
4988        }),
4989    }
4990}
4991
4992fn process_strictness(w: &super::schema::Wildcard) -> u8 {
4993    use super::schema::ProcessContents::*;
4994    match w.process_contents { Strict => 2, Lax => 1, Skip => 0 }
4995}
4996
4997fn check_complex_restriction_attributes(
4998    types:    &HashMap<QName, TypeRef>,
4999    elements: &HashMap<QName, Arc<ElementDecl>>,
5000) -> Result<(), SchemaCompileError> {
5001    use super::schema::AttributeUse;
5002    use super::types::DerivationMethod;
5003
5004    fn resolve_base<'a>(
5005        d: &'a super::types::Derivation,
5006        types: &'a HashMap<QName, TypeRef>,
5007    ) -> Option<&'a ComplexType> {
5008        match &d.base {
5009            TypeRef::Complex(c) => Some(c.as_ref()),
5010            _ => resolve_typeref_to_qname(&d.base).and_then(|qn| match types.get(&qn) {
5011                Some(TypeRef::Complex(c)) => Some(c.as_ref()),
5012                _ => None,
5013            }),
5014        }
5015    }
5016
5017    fn check_one(
5018        ct: &ComplexType,
5019        types: &HashMap<QName, TypeRef>,
5020        label: &str,
5021    ) -> Result<(), SchemaCompileError> {
5022        let Some(d) = &ct.derivation else { return Ok(()); };
5023        if d.method != DerivationMethod::Restriction { return Ok(()); }
5024        let Some(base) = resolve_base(d, types) else { return Ok(()); };
5025
5026        let find_au = |name: &QName, attrs: &[AttributeUse]| -> Option<AttributeUse> {
5027            attrs.iter().find(|au| &au.decl.name == name).cloned()
5028        };
5029
5030        // XSD §3.4.6 cos-ct-derived-ok.2 — when restricting, the
5031        // derived type's `<xs:anyAttribute>` must be a valid subset of
5032        // the base's, and any attribute the derived adds beyond the
5033        // base's attribute set must be allowed by the base's wildcard.
5034        match (&base.any_attribute, &ct.any_attribute) {
5035            (None, Some(_)) => {
5036                return Err(SchemaCompileError::msg(format!(
5037                    "{label}: invalid restriction of base — base has no \
5038                     <xs:anyAttribute> but the derived type adds one (restriction \
5039                     can only tighten, not introduce, attribute wildcards)"
5040                )));
5041            }
5042            (Some(b_any), Some(d_any)) => {
5043                if !ns_constraint_subset(&d_any.namespaces, &b_any.namespaces) {
5044                    return Err(SchemaCompileError::msg(format!(
5045                        "{label}: invalid restriction of base — derived \
5046                         <xs:anyAttribute> namespace is not a subset of base's"
5047                    )));
5048                }
5049                if process_strictness(d_any) < process_strictness(b_any) {
5050                    return Err(SchemaCompileError::msg(format!(
5051                        "{label}: invalid restriction of base — derived \
5052                         <xs:anyAttribute> processContents relaxes the base's"
5053                    )));
5054                }
5055            }
5056            _ => {}
5057        }
5058        // Each derived attribute not present in the base must satisfy
5059        // the base's anyAttribute (XSD §3.4.6 cos-ct-derived-ok.2.2).
5060        let base_attr_names: std::collections::HashSet<&QName> =
5061            base.attributes.iter().map(|au| &au.decl.name).collect();
5062        for au_d in &ct.attributes {
5063            if au_d.use_kind == AttributeUseKind::Prohibited { continue; }
5064            if base_attr_names.contains(&au_d.decl.name) { continue; }
5065            let Some(b_any) = &base.any_attribute else {
5066                return Err(SchemaCompileError::msg(format!(
5067                    "{label}: invalid restriction of base — derived adds \
5068                     attribute {:?} not present in the base, but the base \
5069                     has no <xs:anyAttribute> to license it",
5070                    au_d.decl.name.local,
5071                )));
5072            };
5073            if !wildcard_allows_ns(&b_any.namespaces, au_d.decl.name.namespace.as_deref()) {
5074                return Err(SchemaCompileError::msg(format!(
5075                    "{label}: invalid restriction of base — derived adds \
5076                     attribute {:?} (namespace {:?}) which the base's \
5077                     <xs:anyAttribute> namespace set doesn't allow",
5078                    au_d.decl.name.local, au_d.decl.name.namespace.as_deref().unwrap_or(""),
5079                )));
5080            }
5081        }
5082
5083        for au_b in &base.attributes {
5084            if au_b.use_kind == AttributeUseKind::Prohibited { continue; }
5085            let au_r = find_au(&au_b.decl.name, &ct.attributes);
5086
5087            // Required-in-base must remain required in derived
5088            // (omitted or relaxed → an instance valid under the
5089            // derived type would be invalid under the base, breaking
5090            // the restriction relationship).
5091            if au_b.use_kind == AttributeUseKind::Required {
5092                match au_r.as_ref().map(|x| x.use_kind) {
5093                    Some(AttributeUseKind::Required) => {}
5094                    Some(other) => {
5095                        return Err(SchemaCompileError::msg(format!(
5096                            "{label}: invalid restriction of base — attribute {:?} \
5097                             is `required` in the base but `{:?}` in the derived type",
5098                            au_b.decl.name.local, other,
5099                        )));
5100                    }
5101                    None => {
5102                        return Err(SchemaCompileError::msg(format!(
5103                            "{label}: invalid restriction of base — attribute {:?} \
5104                             is `required` in the base but absent in the derived type",
5105                            au_b.decl.name.local,
5106                        )));
5107                    }
5108                }
5109            }
5110
5111            // Fixed value in the base must be carried through
5112            // unchanged.  An overriding `default=` is rejected: the
5113            // base required the value, the derived doesn't.
5114            let base_fixed = au_b.fixed.as_deref().or(au_b.decl.fixed.as_deref());
5115            if let Some(bf) = base_fixed {
5116                if let Some(au_r) = au_r.as_ref() {
5117                    let r_fixed = au_r.fixed.as_deref().or(au_r.decl.fixed.as_deref());
5118                    let r_default = au_r.default.as_deref().or(au_r.decl.default.as_deref());
5119                    match (r_fixed, r_default) {
5120                        (Some(rf), _) if rf != bf => {
5121                            return Err(SchemaCompileError::msg(format!(
5122                                "{label}: invalid restriction of base — attribute {:?} \
5123                                 fixed value {:?} differs from base fixed {:?}",
5124                                au_b.decl.name.local, rf, bf,
5125                            )));
5126                        }
5127                        (None, Some(_)) => {
5128                            return Err(SchemaCompileError::msg(format!(
5129                                "{label}: invalid restriction of base — attribute {:?} \
5130                                 has `fixed={:?}` in the base; the derived may not \
5131                                 replace it with `default=`",
5132                                au_b.decl.name.local, bf,
5133                            )));
5134                        }
5135                        _ => {}
5136                    }
5137                }
5138            }
5139        }
5140        Ok(())
5141    }
5142
5143    for (name, tr) in types {
5144        if let TypeRef::Complex(ct) = tr {
5145            let label = format!("<xs:complexType name={:?}>", name.local);
5146            check_one(ct, types, &label)?;
5147        }
5148    }
5149    for (name, decl) in elements {
5150        if let TypeRef::Complex(ct) = &decl.type_def {
5151            let label = format!("<xs:element name={:?}>'s inline type", name.local);
5152            check_one(ct, types, &label)?;
5153        }
5154    }
5155    Ok(())
5156}
5157
5158fn check_type_refs_collide(
5159    types:       &HashMap<QName, TypeRef>,
5160    attributes:  &HashMap<QName, Arc<AttributeDecl>>,
5161    attr_groups: &HashMap<QName, Arc<AttributeGroup>>,
5162    elements:    &HashMap<QName, Arc<ElementDecl>>,
5163) -> Result<(), SchemaCompileError> {
5164    fn check_typeref(
5165        tr: &TypeRef,
5166        owner: &str,
5167        types: &HashMap<QName, TypeRef>,
5168        attributes:  &HashMap<QName, Arc<AttributeDecl>>,
5169        attr_groups: &HashMap<QName, Arc<AttributeGroup>>,
5170        elements:    &HashMap<QName, Arc<ElementDecl>>,
5171    ) -> Result<(), SchemaCompileError> {
5172        // Only `UNRESOLVED:` placeholders make it here.  Resolved
5173        // built-ins / inline types / `xs:anyType` return None and
5174        // are silently accepted.
5175        let Some(qn) = resolve_typeref_to_qname(tr) else { return Ok(()) };
5176        if types.contains_key(&qn) { return Ok(()) }
5177        let kind = if attributes.contains_key(&qn) { Some("an attribute") }
5178                   else if attr_groups.contains_key(&qn) { Some("an attributeGroup") }
5179                   else if elements.contains_key(&qn) { Some("an element") }
5180                   else { None };
5181        match kind {
5182            Some(k) => Err(SchemaCompileError::msg(format!(
5183                "{owner}: type={:?} resolves to {k}, not a type",
5184                qn.local,
5185            ))),
5186            // Nothing in the schema declares this name as anything —
5187            // the `<xs:include>` / `<xs:import>` that should have
5188            // brought it in either failed to load or never existed.
5189            None => Err(SchemaCompileError::msg(format!(
5190                "{owner}: undefined type {:?} \
5191                 (no <xs:simpleType> or <xs:complexType> with this name; \
5192                 check that any <xs:include>/<xs:import> schemaLocation \
5193                 was loadable)",
5194                qn.local,
5195            ))),
5196        }
5197    }
5198    fn walk_complex(
5199        ct: &ComplexType,
5200        types: &HashMap<QName, TypeRef>,
5201        attributes:  &HashMap<QName, Arc<AttributeDecl>>,
5202        attr_groups: &HashMap<QName, Arc<AttributeGroup>>,
5203        elements:    &HashMap<QName, Arc<ElementDecl>>,
5204        visited:     &mut std::collections::HashSet<*const ComplexType>,
5205    ) -> Result<(), SchemaCompileError> {
5206        if !visited.insert(ct as *const _) { return Ok(()); }
5207        // XSD §3.4.6 — `<xs:extension>` / `<xs:restriction>` `base="…"`
5208        // must resolve to an existing type definition.  An UNRESOLVED
5209        // placeholder here means the referenced type was never
5210        // declared (or its include / import failed to load).
5211        if let Some(deriv) = ct.derivation.as_ref() {
5212            let label = format!("<xs:complexType name={:?}> base",
5213                ct.name.as_ref().map(|n| &*n.local).unwrap_or("<anonymous>"));
5214            check_typeref(&deriv.base, &label,
5215                          types, attributes, attr_groups, elements)?;
5216        }
5217        for au in &ct.attributes {
5218            check_typeref(&TypeRef::Simple(au.decl.type_def.clone()),
5219                          &format!("<xs:attribute name={:?}>", au.decl.name.local),
5220                          types, attributes, attr_groups, elements)?;
5221        }
5222        if let ContentModel::Complex { root, .. } = &ct.content {
5223            walk_particle(root, types, attributes, attr_groups, elements, visited)?;
5224        }
5225        Ok(())
5226    }
5227    fn walk_particle(
5228        p: &super::schema::Particle,
5229        types: &HashMap<QName, TypeRef>,
5230        attributes:  &HashMap<QName, Arc<AttributeDecl>>,
5231        attr_groups: &HashMap<QName, Arc<AttributeGroup>>,
5232        elements:    &HashMap<QName, Arc<ElementDecl>>,
5233        visited:     &mut std::collections::HashSet<*const ComplexType>,
5234    ) -> Result<(), SchemaCompileError> {
5235        use super::schema::Term;
5236        match &p.term {
5237            Term::Element(e) => {
5238                check_typeref(&e.type_def, &format!("<xs:element name={:?}>", e.name.local),
5239                              types, attributes, attr_groups, elements)?;
5240                if let TypeRef::Complex(ct) = &e.type_def {
5241                    walk_complex(ct, types, attributes, attr_groups, elements, visited)?;
5242                }
5243            }
5244            Term::Group { particles, .. } => {
5245                for child in particles.iter() {
5246                    walk_particle(child, types, attributes, attr_groups, elements, visited)?;
5247                }
5248            }
5249            _ => {}
5250        }
5251        Ok(())
5252    }
5253
5254    let mut visited: std::collections::HashSet<*const ComplexType> = std::collections::HashSet::new();
5255    for (qn, decl) in elements {
5256        let owner = format!("<xs:element name={:?}>", qn.local);
5257        check_typeref(&decl.type_def, &owner, types, attributes, attr_groups, elements)?;
5258        if let TypeRef::Complex(ct) = &decl.type_def {
5259            walk_complex(ct, types, attributes, attr_groups, elements, &mut visited)?;
5260        }
5261    }
5262    for (qn, attr) in attributes {
5263        let owner = format!("<xs:attribute name={:?}>", qn.local);
5264        check_typeref(&TypeRef::Simple(attr.type_def.clone()), &owner,
5265                      types, attributes, attr_groups, elements)?;
5266    }
5267    for ct in types.values().filter_map(|t| match t {
5268        TypeRef::Complex(c) => Some(c),
5269        _ => None,
5270    }) {
5271        walk_complex(ct, types, attributes, attr_groups, elements, &mut visited)?;
5272    }
5273    Ok(())
5274}
5275
5276fn check_element_refs(
5277    refs:        &[QName],
5278    elements:    &HashMap<QName, Arc<ElementDecl>>,
5279    types:       &HashMap<QName, TypeRef>,
5280    attributes:  &HashMap<QName, Arc<AttributeDecl>>,
5281    attr_groups: &HashMap<QName, Arc<AttributeGroup>>,
5282    target_ns:   Option<&str>,
5283    imports:     &HashSet<Option<Arc<str>>>,
5284) -> Result<(), SchemaCompileError> {
5285    for ref_qn in refs {
5286        if elements.contains_key(ref_qn) { continue; }
5287        let collision = if types.contains_key(ref_qn) {
5288            Some("a type")
5289        } else if attributes.contains_key(ref_qn) {
5290            Some("an attribute")
5291        } else if attr_groups.contains_key(ref_qn) {
5292            Some("an attributeGroup")
5293        } else {
5294            None
5295        };
5296        if let Some(kind) = collision {
5297            return Err(SchemaCompileError::msg(format!(
5298                "<xs:element ref={:?}> resolves to {kind}, not an element",
5299                ref_qn.local,
5300            )));
5301        }
5302        // XSD §3.3.6 src-resolve clause 4: a QName ref whose namespace
5303        // differs from this schema's target must have been brought
5304        // into scope by an `<xs:import>`.
5305        if ref_qn.namespace.as_deref() != target_ns
5306            && !import_covers(imports, ref_qn.namespace.as_deref())
5307        {
5308            return Err(SchemaCompileError::msg(format!(
5309                "<xs:element ref={:?}>: namespace {:?} is not the schema's \
5310                 targetNamespace and was not brought in by <xs:import> (XSD §3.3.6 \
5311                 src-resolve)",
5312                ref_qn.local, ref_qn.namespace.as_deref().unwrap_or(""),
5313            )));
5314        }
5315        // Same-namespace ref must point at a *top-level* element
5316        // declaration (XSD §3.3.3 — `ref` only resolves against the
5317        // schema's global element table, never local ones).
5318        if ref_qn.namespace.as_deref() == target_ns {
5319            return Err(SchemaCompileError::msg(format!(
5320                "<xs:element ref={:?}>: no top-level <xs:element name={:?}> \
5321                 declaration in this schema",
5322                ref_qn.local, ref_qn.local,
5323            )));
5324        }
5325    }
5326    Ok(())
5327}
5328
5329fn check_redefined_attribute_groups(
5330    pending: &[(QName, Arc<AttributeGroup>, Arc<AttributeGroup>, bool)],
5331    types:   &HashMap<QName, TypeRef>,
5332) -> Result<(), SchemaCompileError> {
5333    for (name, new_ag, original, saw_self_ref) in pending {
5334        // Self-reference shape mirrors what `check_redefined_groups`
5335        // does for model groups — when the redefining body carried
5336        // a `<xs:attributeGroup ref="same-name"/>` the original's
5337        // attribute uses are spliced in via the ref, and additions
5338        // are XSTS-accepted as an additive redefine.  Skip the
5339        // strict-restriction check for that case.
5340        if *saw_self_ref { continue; }
5341        for new_use in &new_ag.attributes {
5342            let orig_use = original.attributes.iter()
5343                .find(|u| u.decl.name == new_use.decl.name);
5344            let Some(orig_use) = orig_use else {
5345                return Err(SchemaCompileError::msg(format!(
5346                    "<xs:redefine><xs:attributeGroup name={:?}>: attribute {:?} \
5347                     is not present in the original attribute group — a redefining \
5348                     body without a self-reference must be a valid restriction \
5349                     (XSD §4.2.2 src-redefine)",
5350                    name.local, new_use.decl.name.local,
5351                )));
5352            };
5353            // Required attributes from the original must stay
5354            // required (or fixed-by-value); a redefining body can't
5355            // relax `use="required"` to `optional`.
5356            if matches!(orig_use.use_kind, AttributeUseKind::Required)
5357                && matches!(new_use.use_kind,
5358                    AttributeUseKind::Optional | AttributeUseKind::Prohibited)
5359            {
5360                return Err(SchemaCompileError::msg(format!(
5361                    "<xs:redefine><xs:attributeGroup name={:?}>: attribute {:?} \
5362                     was required in the original; the redefining body cannot \
5363                     relax it to {:?}",
5364                    name.local, new_use.decl.name.local, new_use.use_kind,
5365                )));
5366            }
5367            // (Type-derivation check intentionally omitted: chained
5368            // redefines through `<xs:include>` mutate the snapshot in
5369            // ways that make a single-step type comparison fragile;
5370            // the strict-form variants of this rule are not net
5371            // positive against the XSTS corpus.)
5372            let _ = types;
5373            // Prohibited in original must stay prohibited — a
5374            // redefining body can't loosen `use="prohibited"`.
5375            if matches!(orig_use.use_kind, AttributeUseKind::Prohibited)
5376                && !matches!(new_use.use_kind, AttributeUseKind::Prohibited)
5377            {
5378                return Err(SchemaCompileError::msg(format!(
5379                    "<xs:redefine><xs:attributeGroup name={:?}>: attribute {:?} \
5380                     was prohibited in the original; the redefining body cannot \
5381                     relax that to {:?}",
5382                    name.local, new_use.decl.name.local, new_use.use_kind,
5383                )));
5384            }
5385        }
5386        // Every required attribute in the original must still appear
5387        // (with at least the same `use`) in the redefining body —
5388        // dropping a required attribute would leave instances valid
5389        // under derived but invalid under base.
5390        for orig_use in &original.attributes {
5391            if !matches!(orig_use.use_kind, AttributeUseKind::Required) { continue; }
5392            if !new_ag.attributes.iter().any(|u|
5393                u.decl.name == orig_use.decl.name
5394                    && matches!(u.use_kind, AttributeUseKind::Required))
5395            {
5396                return Err(SchemaCompileError::msg(format!(
5397                    "<xs:redefine><xs:attributeGroup name={:?}>: required attribute \
5398                     {:?} is missing in the redefining body — a non-self-reference \
5399                     redefine cannot drop required attributes",
5400                    name.local, orig_use.decl.name.local,
5401                )));
5402            }
5403        }
5404    }
5405    Ok(())
5406}
5407
5408
5409/// Resolve forward-referenced bases captured by
5410/// `parse_simple_restriction` and validate the recorded derived
5411/// facets against the real ancestor facet set.  Same-target-namespace
5412/// references that still don't resolve are flagged here too — they're
5413/// `<xs:restriction base="X">`s pointing at a name that nothing
5414/// declares.
5415fn check_pending_simple_facet_tightening(
5416    pending: &[(QName, Vec<Facet>)],
5417    types:   &HashMap<QName, TypeRef>,
5418) -> Result<(), SchemaCompileError> {
5419    for (base_qn, derived) in pending {
5420        let Some(tr) = types.get(base_qn) else {
5421            return Err(SchemaCompileError::msg(format!(
5422                "<xs:restriction base={:?}>: undefined simple type",
5423                base_qn.local,
5424            )));
5425        };
5426        let base_st = match tr {
5427            TypeRef::Simple(st)  => st.clone(),
5428            TypeRef::Complex(_)  => return Err(SchemaCompileError::msg(format!(
5429                "<xs:restriction base={:?}> in a simpleType must reference a \
5430                 simpleType, not a complexType",
5431                base_qn.local,
5432            ))),
5433        };
5434        // The original parse stashed bound-facet values as
5435        // `Bound::Value(Value::String(...))` because the base built-in
5436        // was unknown.  Now that we know it, re-parse the raw strings
5437        // into the proper numeric / temporal `Bound` shape so
5438        // `compare_bounds` can do a meaningful order check.
5439        let mut reparsed: Vec<Facet> = Vec::with_capacity(derived.len());
5440        for f in derived {
5441            reparsed.push(reparse_bound_facet(f, base_st.builtin)?);
5442        }
5443        check_facet_tightening_pure(&base_st.facets.facets, &reparsed).map_err(|m| {
5444            SchemaCompileError::msg(format!(
5445                "<xs:restriction base={:?}>: {m}", base_qn.local,
5446            ))
5447        })?;
5448    }
5449    Ok(())
5450}
5451
5452fn reparse_bound_facet(f: &Facet, builtin: BuiltinType) -> Result<Facet, SchemaCompileError> {
5453    fn reparse(b: &Bound, builtin: BuiltinType) -> Result<Bound, SchemaCompileError> {
5454        match b {
5455            Bound::Value(super::types::Value::String(s)) => parse_bound(s, builtin),
5456            _ => Ok(b.clone()),
5457        }
5458    }
5459    Ok(match f {
5460        Facet::MinInclusive(b) => Facet::MinInclusive(reparse(b, builtin)?),
5461        Facet::MaxInclusive(b) => Facet::MaxInclusive(reparse(b, builtin)?),
5462        Facet::MinExclusive(b) => Facet::MinExclusive(reparse(b, builtin)?),
5463        Facet::MaxExclusive(b) => Facet::MaxExclusive(reparse(b, builtin)?),
5464        other => other.clone(),
5465    })
5466}
5467
5468/// XSD §4.3 (cvc-restriction) — every facet in `derived` must be a
5469/// legitimate tightening of the corresponding facet in `base`.
5470/// Module-scope so the parser's inline check and the post-pass that
5471/// validates forward-referenced bases share a single implementation.
5472fn check_facet_tightening_pure(base: &[Facet], derived: &[Facet]) -> Result<(), String> {
5473    fn pick<'a, T, F>(slice: &'a [Facet], f: F) -> Option<&'a T>
5474    where F: Fn(&'a Facet) -> Option<&'a T> {
5475        slice.iter().rev().find_map(f)
5476    }
5477    let base_length    = pick(base, |f| if let Facet::Length(n)    = f { Some(n) } else { None });
5478    let base_min_len   = pick(base, |f| if let Facet::MinLength(n) = f { Some(n) } else { None });
5479    let base_max_len   = pick(base, |f| if let Facet::MaxLength(n) = f { Some(n) } else { None });
5480    let base_min_incl  = pick(base, |f| if let Facet::MinInclusive(b) = f { Some(b) } else { None });
5481    let base_max_incl  = pick(base, |f| if let Facet::MaxInclusive(b) = f { Some(b) } else { None });
5482    let base_min_excl  = pick(base, |f| if let Facet::MinExclusive(b) = f { Some(b) } else { None });
5483    let base_max_excl  = pick(base, |f| if let Facet::MaxExclusive(b) = f { Some(b) } else { None });
5484    let base_total_d   = pick(base, |f| if let Facet::TotalDigits(n) = f { Some(n) } else { None });
5485    let base_frac_d    = pick(base, |f| if let Facet::FractionDigits(n) = f { Some(n) } else { None });
5486    for f in derived {
5487        match f {
5488            Facet::Length(n) => {
5489                if let Some(b) = base_length {
5490                    if n != b {
5491                        return Err(format!("restriction length ({n}) must equal base length ({b})"));
5492                    }
5493                }
5494                if let Some(b) = base_min_len {
5495                    if (*n as usize) < (*b) {
5496                        return Err(format!("restriction length ({n}) is below base minLength ({b})"));
5497                    }
5498                }
5499                if let Some(b) = base_max_len {
5500                    if (*n as usize) > (*b) {
5501                        return Err(format!("restriction length ({n}) exceeds base maxLength ({b})"));
5502                    }
5503                }
5504            }
5505            Facet::MinLength(n) => {
5506                if let Some(b) = base_min_len {
5507                    if n < b {
5508                        return Err(format!("restriction minLength ({n}) is below base minLength ({b})"));
5509                    }
5510                }
5511                if let Some(b) = base_length {
5512                    if (*n as u32) != (*b as u32) {
5513                        return Err(format!("restriction minLength ({n}) is inconsistent with base length ({b})"));
5514                    }
5515                }
5516            }
5517            Facet::MaxLength(n) => {
5518                if let Some(b) = base_max_len {
5519                    if n > b {
5520                        return Err(format!("restriction maxLength ({n}) exceeds base maxLength ({b})"));
5521                    }
5522                }
5523                if let Some(b) = base_length {
5524                    if (*n as u32) != (*b as u32) {
5525                        return Err(format!("restriction maxLength ({n}) is inconsistent with base length ({b})"));
5526                    }
5527                }
5528            }
5529            Facet::MinInclusive(d) => {
5530                if let Some(b) = base_min_incl {
5531                    if compare_bounds(d, b).map(|o| o.is_lt()).unwrap_or(false) {
5532                        return Err("restriction minInclusive is below the base's min bound".into());
5533                    }
5534                }
5535                if let Some(b) = base_min_excl {
5536                    // d ≤ b means derived admits a value (d itself or
5537                    // anything ≤ b) that the base's exclusive bound
5538                    // forbids.
5539                    if compare_bounds(d, b).map(|o| !o.is_gt()).unwrap_or(false) {
5540                        return Err("restriction minInclusive is at or below the base's minExclusive bound".into());
5541                    }
5542                }
5543                if let Some(b) = base_max_incl {
5544                    if compare_bounds(d, b).map(|o| o.is_gt()).unwrap_or(false) {
5545                        return Err("restriction minInclusive exceeds the base's max bound".into());
5546                    }
5547                }
5548                if let Some(b) = base_max_excl {
5549                    // d ≥ b means the derived inclusive bound admits a
5550                    // value the base's exclusive max forbids.
5551                    if compare_bounds(d, b).map(|o| !o.is_lt()).unwrap_or(false) {
5552                        return Err("restriction minInclusive is at or above the base's maxExclusive bound".into());
5553                    }
5554                }
5555            }
5556            Facet::MinExclusive(d) => {
5557                for b in base_min_incl.into_iter().chain(base_min_excl) {
5558                    if compare_bounds(d, b).map(|o| o.is_lt()).unwrap_or(false) {
5559                        return Err("restriction minExclusive is below the base's min bound".into());
5560                    }
5561                }
5562                for b in base_max_incl.into_iter().chain(base_max_excl) {
5563                    if compare_bounds(d, b).map(|o| o.is_ge()).unwrap_or(false) {
5564                        return Err("restriction minExclusive is at or above the base's max bound".into());
5565                    }
5566                }
5567            }
5568            Facet::MaxInclusive(d) => {
5569                if let Some(b) = base_max_incl {
5570                    if compare_bounds(d, b).map(|o| o.is_gt()).unwrap_or(false) {
5571                        return Err("restriction maxInclusive exceeds the base's max bound".into());
5572                    }
5573                }
5574                if let Some(b) = base_max_excl {
5575                    // d ≥ b means derived admits a value (d itself or
5576                    // anything ≥ b) that the base's exclusive bound
5577                    // forbids.
5578                    if compare_bounds(d, b).map(|o| !o.is_lt()).unwrap_or(false) {
5579                        return Err("restriction maxInclusive is at or above the base's maxExclusive bound".into());
5580                    }
5581                }
5582                for b in base_min_incl.into_iter().chain(base_min_excl) {
5583                    if compare_bounds(d, b).map(|o| o.is_lt()).unwrap_or(false) {
5584                        return Err("restriction maxInclusive is below the base's min bound".into());
5585                    }
5586                }
5587            }
5588            Facet::MaxExclusive(d) => {
5589                for b in base_max_incl.into_iter().chain(base_max_excl) {
5590                    if compare_bounds(d, b).map(|o| o.is_gt()).unwrap_or(false) {
5591                        return Err("restriction maxExclusive exceeds the base's max bound".into());
5592                    }
5593                }
5594                for b in base_min_incl.into_iter().chain(base_min_excl) {
5595                    if compare_bounds(d, b).map(|o| o.is_le()).unwrap_or(false) {
5596                        return Err("restriction maxExclusive is at or below the base's min bound".into());
5597                    }
5598                }
5599            }
5600            Facet::TotalDigits(n) => {
5601                if let Some(b) = base_total_d {
5602                    if n > b {
5603                        return Err(format!("restriction totalDigits ({n}) exceeds base totalDigits ({b})"));
5604                    }
5605                }
5606            }
5607            Facet::FractionDigits(n) => {
5608                if let Some(b) = base_frac_d {
5609                    if n > b {
5610                        return Err(format!("restriction fractionDigits ({n}) exceeds base fractionDigits ({b})"));
5611                    }
5612                }
5613            }
5614            _ => {}
5615        }
5616    }
5617    Ok(())
5618}
5619
5620fn import_covers(imports: &HashSet<Option<Arc<str>>>, ns: Option<&str>) -> bool {
5621    match ns {
5622        None    => imports.iter().any(|entry| entry.is_none()),
5623        Some(s) => imports.iter().any(|entry| entry.as_deref() == Some(s)),
5624    }
5625}
5626
5627/// Verify that no element declaration with `default=` / `fixed=`
5628/// has a (transitively-resolved) type whose builtin chain reaches
5629/// `xs:ID` — XSD §3.3.6 forbids value constraints on ID-typed
5630/// elements.  Runs after type resolution so chains through
5631/// user-defined `<xs:simpleType>` are fully visible.
5632fn check_id_typed_element_value_constraints(
5633    elements: &HashMap<QName, Arc<ElementDecl>>,
5634    types:    &HashMap<QName, TypeRef>,
5635) -> Result<(), SchemaCompileError> {
5636    fn id_typed_simple(st: &super::types::SimpleType, types: &HashMap<QName, TypeRef>) -> bool {
5637        if matches!(st.builtin, BuiltinType::Id) { return true; }
5638        // Walk the named-type chain for a deeper resolved view.  The
5639        // parser collapses `<xs:simpleType><xs:restriction base="X">`
5640        // into a SimpleType carrying X's builtin, so for most chains
5641        // `st.builtin == Id` already.  This loop just handles the
5642        // case where the name still points at a top-level user-named
5643        // simple type that wasn't fully merged in.
5644        if let Some(name) = &st.name {
5645            if let Some(rest) = name.strip_prefix("UNRESOLVED:") {
5646                let qn = if let Some(rest) = rest.strip_prefix('{') {
5647                    if let Some(end) = rest.find('}') {
5648                        let ns    = &rest[..end];
5649                        let local = &rest[end + 1..];
5650                        QName::new(if ns.is_empty() { None } else { Some(ns) }, local)
5651                    } else { QName::new(None, rest) }
5652                } else { QName::new(None, rest) };
5653                if let Some(TypeRef::Simple(real)) = types.get(&qn) {
5654                    return id_typed_simple(real, types);
5655                }
5656            }
5657        }
5658        false
5659    }
5660    for decl in elements.values() {
5661        if decl.default.is_none() && decl.fixed.is_none() { continue; }
5662        if let TypeRef::Simple(st) = &decl.type_def {
5663            if id_typed_simple(st, types) {
5664                return Err(SchemaCompileError::msg(format!(
5665                    "<xs:element name={:?}> of type xs:ID (or derived) cannot have \
5666                     a default or fixed value (XSD §3.3.6)",
5667                    decl.name.local,
5668                )));
5669            }
5670        }
5671    }
5672    Ok(())
5673}
5674
5675/// XSD §3.8.6 *Element Declarations Consistent*: within a single
5676/// content model, every element-particle declaration with a given
5677/// `{name}` must have the same `{type definition}` (judged by Arc
5678/// identity, then by declared-name equality).  Walks the particle
5679/// tree across nested groups, collecting `name → typeref` pairs.
5680fn check_element_decls_consistent(
5681    types:    &HashMap<QName, TypeRef>,
5682    elements: &HashMap<QName, Arc<ElementDecl>>,
5683) -> Result<(), SchemaCompileError> {
5684    fn typerefs_compatible(a: &TypeRef, b: &TypeRef) -> bool {
5685        match (a, b) {
5686            (TypeRef::Simple(x), TypeRef::Simple(y)) => {
5687                if Arc::ptr_eq(x, y) { return true; }
5688                // Two parser-built built-in references share no Arc but
5689                // are the same type when their `builtin` matches and
5690                // neither carries a user-defined name.
5691                if x.name.is_none() && y.name.is_none()
5692                    && x.builtin == y.builtin
5693                    && matches!(x.variety, super::types::Variety::Atomic)
5694                    && matches!(y.variety, super::types::Variety::Atomic)
5695                {
5696                    return true;
5697                }
5698                x.name.is_some() && x.name == y.name
5699            }
5700            (TypeRef::Complex(x), TypeRef::Complex(y)) => Arc::ptr_eq(x, y)
5701                || (x.name.is_some() && x.name == y.name),
5702            _ => false,
5703        }
5704    }
5705    fn walk(
5706        p:    &super::schema::Particle,
5707        seen: &mut HashMap<QName, TypeRef>,
5708        owner: &str,
5709    ) -> Result<(), SchemaCompileError> {
5710        match &p.term {
5711            super::schema::Term::Element(decl) => {
5712                if let Some(prev) = seen.get(&decl.name) {
5713                    if !typerefs_compatible(prev, &decl.type_def) {
5714                        return Err(SchemaCompileError::msg(format!(
5715                            "{owner}: element {:?} appears twice in the content \
5716                             model with different types (XSD §3.8.6 Element \
5717                             Declarations Consistent)",
5718                            decl.name.local,
5719                        )));
5720                    }
5721                } else {
5722                    seen.insert(decl.name.clone(), decl.type_def.clone());
5723                }
5724            }
5725            super::schema::Term::Group { particles, .. } => {
5726                for c in particles.iter() { walk(c, seen, owner)?; }
5727            }
5728            super::schema::Term::Wildcard(_)
5729            | super::schema::Term::GroupRef(_) => {}
5730        }
5731        Ok(())
5732    }
5733    let check_complex = |ct: &ComplexType| -> Result<(), SchemaCompileError> {
5734        if let ContentModel::Complex { root, .. } = &ct.content {
5735            let mut seen: HashMap<QName, TypeRef> = HashMap::new();
5736            let owner = format!("<xs:complexType name={:?}>",
5737                ct.name.as_ref().map(|n| &*n.local).unwrap_or("<anonymous>"));
5738            walk(root, &mut seen, &owner)?;
5739        }
5740        Ok(())
5741    };
5742    for tr in types.values() {
5743        if let TypeRef::Complex(ct) = tr { check_complex(ct)?; }
5744    }
5745    for decl in elements.values() {
5746        if let TypeRef::Complex(ct) = &decl.type_def { check_complex(ct)?; }
5747    }
5748    Ok(())
5749}
5750
5751fn check_substitution_group_typing(
5752    elements: &HashMap<QName, Arc<ElementDecl>>,
5753    types:    &HashMap<QName, TypeRef>,
5754) -> Result<(), SchemaCompileError> {
5755    fn is_any_type(tr: &TypeRef) -> bool {
5756        matches!(tr, TypeRef::Complex(ct)
5757            if ct.name.as_ref()
5758                .map(|n| n.namespace.as_deref() == Some(QName::XSD_NS)
5759                         && n.local.as_ref() == "anyType")
5760                .unwrap_or(false))
5761    }
5762    for sub in elements.values() {
5763        let Some(head_qn) = &sub.substitution_group else { continue };
5764        let Some(head) = elements.get(head_qn) else { continue };
5765
5766        // XSD §3.3.2 — an element with a substitutionGroup whose
5767        // `type=` is omitted inherits the head's type.  We can't
5768        // distinguish "explicitly typed as xs:anyType" from "no type
5769        // attribute" post-parse, but xs:anyType in an instance schema
5770        // is rare enough that treating it as the inherited default
5771        // covers the common case without over-rejecting.
5772        if is_any_type(&sub.type_def) {
5773            continue;
5774        }
5775
5776        let used = super::dfa::derivation_methods_between(
5777            &sub.type_def, &head.type_def, types,
5778        );
5779        let Some(used) = used else {
5780            return Err(SchemaCompileError::msg(format!(
5781                "<xs:element name={:?} substitutionGroup={:?}>: \
5782                 substituting element's type does not derive from the head's type",
5783                sub.name.local, head_qn.local,
5784            )));
5785        };
5786
5787        // Substitution-group exclusions per cvc-elt-substitution =
5788        // head element's final + head type's final.  The schema-level
5789        // finalDefault was folded into both at parse time.
5790        let head_type_final = match &head.type_def {
5791            TypeRef::Complex(ct) => ct.final_,
5792            TypeRef::Simple(_)   => BlockSet::default(),
5793        };
5794        let blocked = head.final_ | head_type_final;
5795        let forbidden = used & blocked & (BlockSet::RESTRICTION | BlockSet::EXTENSION);
5796        if !forbidden.is_empty() {
5797            let label = if forbidden.contains(BlockSet::RESTRICTION) && forbidden.contains(BlockSet::EXTENSION) {
5798                "restriction and extension"
5799            } else if forbidden.contains(BlockSet::RESTRICTION) {
5800                "restriction"
5801            } else {
5802                "extension"
5803            };
5804            return Err(SchemaCompileError::msg(format!(
5805                "<xs:element name={:?} substitutionGroup={:?}>: \
5806                 head element's `final` disallows {label}-based substitution",
5807                sub.name.local, head_qn.local,
5808            )));
5809        }
5810    }
5811    Ok(())
5812}
5813
5814fn check_substitution_group_heads(
5815    elements: &HashMap<QName, Arc<ElementDecl>>,
5816) -> Result<(), SchemaCompileError> {
5817    for decl in elements.values() {
5818        let Some(head) = &decl.substitution_group else { continue };
5819        if !elements.contains_key(head) {
5820            return Err(SchemaCompileError::msg(format!(
5821                "<xs:element name={:?} substitutionGroup={:?}>: \
5822                 the head element is not declared",
5823                decl.name.local, head.local,
5824            )));
5825        }
5826    }
5827    // XSD §3.3.6 cos-equiv-class-correct — substitution groups must
5828    // form a forest (no cycles).  Walk each element's substitution
5829    // chain and reject when a name repeats.
5830    for decl in elements.values() {
5831        let mut seen: HashSet<QName> = HashSet::new();
5832        let mut cur = decl.clone();
5833        loop {
5834            let Some(head_qn) = &cur.substitution_group else { break };
5835            if !seen.insert(cur.name.clone()) {
5836                return Err(SchemaCompileError::msg(format!(
5837                    "<xs:element name={:?}>: substitution-group chain is cyclic \
5838                     (eventually revisits {:?}); per XSD §3.3.6 cos-equiv-class-correct \
5839                     the substitution graph must be a forest",
5840                    decl.name.local, cur.name.local,
5841                )));
5842            }
5843            if head_qn == &cur.name {
5844                return Err(SchemaCompileError::msg(format!(
5845                    "<xs:element name={:?}>: cannot substitute for itself",
5846                    cur.name.local,
5847                )));
5848            }
5849            let Some(next) = elements.get(head_qn) else { break };
5850            cur = next.clone();
5851        }
5852    }
5853    Ok(())
5854}
5855
5856/// XSD §3.2.5 — the XML Schema Instance namespace
5857/// (`http://www.w3.org/2001/XMLSchema-instance`) is reserved for the
5858/// four pre-declared attributes (`xsi:type`, `xsi:nil`,
5859/// `xsi:schemaLocation`, `xsi:noNamespaceSchemaLocation`).  User
5860/// schemas cannot declare attributes in that namespace.
5861fn check_xsi_attribute_name(qn: &QName) -> Result<(), String> {
5862    match qn.namespace.as_deref() {
5863        Some("http://www.w3.org/2001/XMLSchema-instance") => Err(format!(
5864            "<xs:attribute name={:?}> in the XML Schema Instance namespace is not allowed",
5865            qn.local,
5866        )),
5867        _ => Ok(()),
5868    }
5869}
5870
5871fn check_attribute_group_cycles(
5872    edges: &HashMap<QName, Vec<QName>>,
5873) -> Result<(), SchemaCompileError> {
5874    enum Color { White, Gray, Black }
5875    let mut color: HashMap<&QName, Color> = edges.keys().map(|k| (k, Color::White)).collect();
5876    fn dfs<'a>(
5877        node:  &'a QName,
5878        edges: &'a HashMap<QName, Vec<QName>>,
5879        color: &mut HashMap<&'a QName, Color>,
5880    ) -> Result<(), QName> {
5881        color.insert(node, Color::Gray);
5882        if let Some(refs) = edges.get(node) {
5883            for r in refs {
5884                // Find the key in `edges` that equals `r` by value.
5885                let next_key = edges.keys().find(|k| *k == r);
5886                let Some(next) = next_key else { continue };
5887                match color.get(next) {
5888                    Some(Color::Gray)  => return Err(node.clone()),
5889                    Some(Color::Black) => {}
5890                    Some(Color::White) | None => dfs(next, edges, color)?,
5891                }
5892            }
5893        }
5894        color.insert(node, Color::Black);
5895        Ok(())
5896    }
5897    let names: Vec<&QName> = edges.keys().collect();
5898    for n in names {
5899        if matches!(color.get(n), Some(Color::White)) {
5900            if let Err(cyc) = dfs(n, edges, &mut color) {
5901                return Err(SchemaCompileError::msg(format!(
5902                    "cyclic <xs:attributeGroup> reference involving {:?} \
5903                     (XSD §3.6.6 src-attribute-group-cyclic)",
5904                    cyc.local,
5905                )));
5906            }
5907        }
5908    }
5909    Ok(())
5910}
5911
5912fn check_ag_refs_in_ag(
5913    refs:        &[QName],
5914    attr_groups: &HashMap<QName, Arc<AttributeGroup>>,
5915    types:       &HashMap<QName, TypeRef>,
5916    elements:    &HashMap<QName, Arc<ElementDecl>>,
5917    attributes:  &HashMap<QName, Arc<AttributeDecl>>,
5918) -> Result<(), SchemaCompileError> {
5919    for ref_qn in refs {
5920        if attr_groups.contains_key(ref_qn) { continue; }
5921        let collision = if types.contains_key(ref_qn) {
5922            Some("a type")
5923        } else if elements.contains_key(ref_qn) {
5924            Some("an element")
5925        } else if attributes.contains_key(ref_qn) {
5926            Some("an attribute")
5927        } else {
5928            None
5929        };
5930        if let Some(kind) = collision {
5931            return Err(SchemaCompileError::msg(format!(
5932                "<xs:attributeGroup ref={:?}> resolves to {kind}, not an attributeGroup",
5933                ref_qn.local,
5934            )));
5935        }
5936    }
5937    Ok(())
5938}
5939
5940fn check_attribute_refs(
5941    refs:        &[QName],
5942    attributes:  &HashMap<QName, Arc<AttributeDecl>>,
5943    attr_groups: &HashMap<QName, Arc<AttributeGroup>>,
5944    types:       &HashMap<QName, TypeRef>,
5945    elements:    &HashMap<QName, Arc<ElementDecl>>,
5946    target_ns:   Option<&str>,
5947    imports:     &HashSet<Option<Arc<str>>>,
5948) -> Result<(), SchemaCompileError> {
5949    for ref_qn in refs {
5950        if attributes.contains_key(ref_qn) { continue; }
5951        let collision = if attr_groups.contains_key(ref_qn) {
5952            Some("an attributeGroup")
5953        } else if types.contains_key(ref_qn) {
5954            Some("a type")
5955        } else if elements.contains_key(ref_qn) {
5956            Some("an element")
5957        } else {
5958            None
5959        };
5960        if let Some(kind) = collision {
5961            return Err(SchemaCompileError::msg(format!(
5962                "<xs:attribute ref={:?}> resolves to {kind}, not an attribute",
5963                ref_qn.local,
5964            )));
5965        }
5966        // XSD §3.2.3 src-resolve clause 4 — foreign-namespace refs
5967        // must be brought in by an `<xs:import>`.
5968        if ref_qn.namespace.as_deref() != target_ns
5969            && !import_covers(imports, ref_qn.namespace.as_deref())
5970        {
5971            return Err(SchemaCompileError::msg(format!(
5972                "<xs:attribute ref={:?}>: namespace {:?} is not the schema's \
5973                 targetNamespace and was not brought in by <xs:import> (XSD §3.2.3 \
5974                 src-resolve)",
5975                ref_qn.local, ref_qn.namespace.as_deref().unwrap_or(""),
5976            )));
5977        }
5978        // Same-namespace ref must point at a *top-level* attribute
5979        // declaration (XSD §3.2.3 — `ref` only resolves against the
5980        // schema's global attribute table, never local ones).
5981        if ref_qn.namespace.as_deref() == target_ns {
5982            return Err(SchemaCompileError::msg(format!(
5983                "<xs:attribute ref={:?}>: no top-level <xs:attribute name={:?}> \
5984                 declaration in this schema",
5985                ref_qn.local, ref_qn.local,
5986            )));
5987        }
5988    }
5989    Ok(())
5990}
5991
5992fn check_attribute_group_ref_kinds(
5993    types:       &HashMap<QName, TypeRef>,
5994    elements:    &HashMap<QName, Arc<ElementDecl>>,
5995    attr_groups: &HashMap<QName, Arc<AttributeGroup>>,
5996    attributes:  &HashMap<QName, Arc<AttributeDecl>>,
5997) -> Result<(), SchemaCompileError> {
5998    let check_refs = |refs: &[QName], owner: &str| -> Result<(), SchemaCompileError> {
5999        for ref_qn in refs {
6000            // The ref resolves successfully against attr_groups —
6001            // perfect. Otherwise, only flag if it collides with a
6002            // *different* kind of declaration (which is the real
6003            // schema validity error). Unresolvable refs (no match
6004            // anywhere) are tolerated for cross-schema soft-skip
6005            // compatibility.
6006            if attr_groups.contains_key(ref_qn) { continue; }
6007            let collision = if types.contains_key(ref_qn) {
6008                Some("a type")
6009            } else if elements.contains_key(ref_qn) {
6010                Some("an element")
6011            } else if attributes.contains_key(ref_qn) {
6012                Some("an attribute")
6013            } else {
6014                None
6015            };
6016            if let Some(kind) = collision {
6017                return Err(SchemaCompileError::msg(format!(
6018                    "<xs:attributeGroup ref={:?}> in {owner} resolves to {kind}, \
6019                     not an attributeGroup",
6020                    ref_qn.local,
6021                )));
6022            }
6023        }
6024        Ok(())
6025    };
6026    for (name, tr) in types {
6027        if let TypeRef::Complex(ct) = tr {
6028            check_refs(&ct.pending_attribute_group_refs, &format!("complex type {:?}", name.local))?;
6029        }
6030    }
6031    for (name, decl) in elements {
6032        if let TypeRef::Complex(ct) = &decl.type_def {
6033            check_refs(&ct.pending_attribute_group_refs, &format!("element {:?}", name.local))?;
6034        }
6035    }
6036    Ok(())
6037}
6038
6039fn check_element_value_constraints(
6040    types:    &HashMap<QName, TypeRef>,
6041    elements: &HashMap<QName, Arc<ElementDecl>>,
6042) -> Result<(), SchemaCompileError> {
6043    for decl in elements.values() {
6044        let Some(v) = decl.default.as_deref().or(decl.fixed.as_deref()) else { continue };
6045        // Walk the type_def, following UNRESOLVED placeholders into
6046        // the types map until we reach the concrete type.
6047        let resolved: Option<TypeRef> = match &decl.type_def {
6048            TypeRef::Complex(_) => Some(decl.type_def.clone()),
6049            TypeRef::Simple(_)  => match resolve_typeref_to_qname(&decl.type_def) {
6050                Some(qn) => types.get(&qn).cloned().or_else(|| Some(decl.type_def.clone())),
6051                None     => Some(decl.type_def.clone()),
6052            },
6053        };
6054        let Some(resolved) = resolved else { continue };
6055        let allowed = match &resolved {
6056            TypeRef::Simple(_) => true,
6057            TypeRef::Complex(ct) => match &ct.content {
6058                ContentModel::Simple(_) => true,
6059                ContentModel::Complex { mixed, .. } => *mixed,
6060                ContentModel::Empty => false,
6061            },
6062        };
6063        if !allowed {
6064            return Err(SchemaCompileError::msg(format!(
6065                "<xs:element name={:?}> with element-only or empty content cannot \
6066                 have a default/fixed value of {v:?} (XSD §3.3.3)",
6067                decl.name.local,
6068            )));
6069        }
6070        // XSD §3.3.3 cvc-elt-value: the default/fixed string must
6071        // validate against the element's simple type (or the simple
6072        // base of a simpleContent complex type).
6073        let simple = match &resolved {
6074            TypeRef::Simple(st) => Some(st.clone()),
6075            TypeRef::Complex(ct) => match &ct.content {
6076                ContentModel::Simple(st) => Some(st.clone()),
6077                _ => None,
6078            },
6079        };
6080        if let Some(st) = simple {
6081            if let Err(e) = st.validate(v) {
6082                return Err(SchemaCompileError::msg(format!(
6083                    "<xs:element name={:?}> default/fixed value {v:?} is not \
6084                     valid for its type: {}",
6085                    decl.name.local, e.message,
6086                )));
6087            }
6088        }
6089    }
6090    Ok(())
6091}
6092
6093fn check_keyref_refer(
6094    elements: &HashMap<QName, Arc<ElementDecl>>,
6095) -> Result<(), SchemaCompileError> {
6096    use super::identity::ConstraintKind;
6097    // Walk a particle tree, calling `visit` on every element decl
6098    // we touch (including local elements nested in complex types).
6099    fn walk_particle(p: &super::schema::Particle, visit: &mut dyn FnMut(&ElementDecl)) {
6100        match &p.term {
6101            super::schema::Term::Element(decl) => {
6102                visit(decl);
6103                if let TypeRef::Complex(ct) = &decl.type_def {
6104                    if let super::schema::ContentModel::Complex { root, .. } = &ct.content {
6105                        walk_particle(root, visit);
6106                    }
6107                }
6108            }
6109            super::schema::Term::Group { particles, .. } => {
6110                for p in particles.iter() { walk_particle(p, visit); }
6111            }
6112            _ => {}
6113        }
6114    }
6115    fn for_every_decl(
6116        elements: &HashMap<QName, Arc<ElementDecl>>,
6117        mut visit: impl FnMut(&ElementDecl),
6118    ) {
6119        for top in elements.values() {
6120            visit(top);
6121            if let TypeRef::Complex(ct) = &top.type_def {
6122                if let super::schema::ContentModel::Complex { root, .. } = &ct.content {
6123                    walk_particle(root, &mut visit);
6124                }
6125            }
6126        }
6127    }
6128
6129    // First pass — collect every key/unique's name + field count
6130    // across all element decls reachable from the global set.
6131    let mut keys: HashMap<QName, usize> = HashMap::new();
6132    for_every_decl(elements, |decl| {
6133        for ic in &decl.identity {
6134            if matches!(ic.kind, ConstraintKind::Key | ConstraintKind::Unique) {
6135                keys.insert(ic.name.clone(), ic.fields.len());
6136            }
6137        }
6138    });
6139    // Second pass — every keyref's `refer` must resolve, and its
6140    // field count must match the referenced key's (XSD §3.11.6
6141    // src-identity-constraint).
6142    let mut dangling: Option<(QName, QName, usize, Option<usize>)> = None;
6143    for_every_decl(elements, |decl| {
6144        if dangling.is_some() { return; }
6145        for ic in &decl.identity {
6146            if !matches!(ic.kind, ConstraintKind::KeyRef) { continue; }
6147            let Some(refer) = ic.refer.as_ref() else { continue };
6148            match keys.get(refer) {
6149                None => {
6150                    dangling = Some((ic.name.clone(), refer.clone(), ic.fields.len(), None));
6151                    return;
6152                }
6153                Some(&refer_fields) if ic.fields.len() != refer_fields => {
6154                    dangling = Some((ic.name.clone(), refer.clone(), ic.fields.len(),
6155                        Some(refer_fields)));
6156                    return;
6157                }
6158                _ => {}
6159            }
6160        }
6161    });
6162    if let Some((name, refer, df, rf)) = dangling {
6163        return match rf {
6164            None => Err(SchemaCompileError::msg(format!(
6165                "<xs:keyref name={:?} refer={:?}>: \
6166                 the referenced key/unique is not declared",
6167                name.local, refer.local,
6168            ))),
6169            Some(rf) => Err(SchemaCompileError::msg(format!(
6170                "<xs:keyref name={:?} refer={:?}>: declares {df} \
6171                 <xs:field> child(ren), but the referenced key has {rf}",
6172                name.local, refer.local,
6173            ))),
6174        };
6175    }
6176    Ok(())
6177}
6178
6179fn check_attribute_type_kinds(
6180    types:      &HashMap<QName, TypeRef>,
6181    attributes: &HashMap<QName, Arc<AttributeDecl>>,
6182    elements:   &HashMap<QName, Arc<ElementDecl>>,
6183) -> Result<(), SchemaCompileError> {
6184    fn check_one(
6185        decl: &AttributeDecl,
6186        types: &HashMap<QName, TypeRef>,
6187    ) -> Result<(), SchemaCompileError> {
6188        let resolved_type: Arc<SimpleType> =
6189            if let Some(qn) = resolve_typeref_to_qname(&TypeRef::Simple(decl.type_def.clone())) {
6190                match types.get(&qn) {
6191                    Some(TypeRef::Complex(_)) => return Err(SchemaCompileError::msg(format!(
6192                        "<xs:attribute name={:?} type={:?}> — attribute type must be a simple type, not a complex type",
6193                        decl.name.local, qn.local,
6194                    ))),
6195                    Some(TypeRef::Simple(real)) => real.clone(),
6196                    None => decl.type_def.clone(),
6197                }
6198            } else {
6199                decl.type_def.clone()
6200            };
6201        // XSD §3.2.6 — an attribute of type xs:ID (or derived from
6202        // xs:ID) cannot carry default or fixed: ID validity is
6203        // per-instance, so a fixed/default ID would be illegal as
6204        // soon as the attribute appeared twice.
6205        if matches!(resolved_type.builtin, super::types::BuiltinType::Id)
6206            && (decl.default.is_some() || decl.fixed.is_some())
6207        {
6208            return Err(SchemaCompileError::msg(format!(
6209                "<xs:attribute name={:?}> of type xs:ID cannot have a default or fixed value",
6210                decl.name.local,
6211            )));
6212        }
6213        // XSD §3.2.3 (cvc-attribute-3) — `default` and `fixed`
6214        // values must validate against the attribute's type.
6215        for (label, raw) in [
6216            ("default", decl.default.as_deref()),
6217            ("fixed",   decl.fixed.as_deref()),
6218        ] {
6219            let Some(raw) = raw else { continue };
6220            if let Err(e) = resolved_type.validate(raw) {
6221                return Err(SchemaCompileError::msg(format!(
6222                    "<xs:attribute name={:?} {label}={raw:?}> is not valid for its type: {}",
6223                    decl.name.local, e.message,
6224                )));
6225            }
6226        }
6227        Ok(())
6228    }
6229    fn walk_complex(
6230        ct: &ComplexType,
6231        types: &HashMap<QName, TypeRef>,
6232        visited: &mut std::collections::HashSet<*const ComplexType>,
6233    ) -> Result<(), SchemaCompileError> {
6234        if !visited.insert(ct as *const _) {
6235            return Ok(());
6236        }
6237        for au in &ct.attributes {
6238            check_one(&au.decl, types)?;
6239        }
6240        if let ContentModel::Complex { root, .. } = &ct.content {
6241            walk_particle(root, types, visited)?;
6242        }
6243        Ok(())
6244    }
6245    fn walk_particle(
6246        p: &super::schema::Particle,
6247        types: &HashMap<QName, TypeRef>,
6248        visited: &mut std::collections::HashSet<*const ComplexType>,
6249    ) -> Result<(), SchemaCompileError> {
6250        use super::schema::Term;
6251        match &p.term {
6252            Term::Element(e) => {
6253                if let TypeRef::Complex(ct) = &e.type_def {
6254                    walk_complex(ct, types, visited)?;
6255                }
6256            }
6257            Term::Group { particles, .. } => {
6258                for child in particles.iter() {
6259                    walk_particle(child, types, visited)?;
6260                }
6261            }
6262            Term::Wildcard(_) | Term::GroupRef(_) => {}
6263        }
6264        Ok(())
6265    }
6266
6267    for decl in attributes.values() {
6268        check_one(decl, types)?;
6269    }
6270    let mut visited: std::collections::HashSet<*const ComplexType> = std::collections::HashSet::new();
6271    for ct in types.values().filter_map(|t| match t {
6272        TypeRef::Complex(c) => Some(c),
6273        _ => None,
6274    }) {
6275        walk_complex(ct, types, &mut visited)?;
6276    }
6277    for decl in elements.values() {
6278        if let TypeRef::Complex(ct) = &decl.type_def {
6279            walk_complex(ct, types, &mut visited)?;
6280        }
6281    }
6282    Ok(())
6283}
6284
6285fn resolve_typeref_to_qname(tr: &TypeRef) -> Option<QName> {
6286    if let TypeRef::Simple(st) = tr {
6287        if let Some(name) = &st.name {
6288            if let Some(rest) = name.strip_prefix("UNRESOLVED:") {
6289                // Same format as parse_unresolved_marker in validate.rs:
6290                // `{ns}local` or just `local`.
6291                if let Some(rest) = rest.strip_prefix('{') {
6292                    if let Some(end) = rest.find('}') {
6293                        let ns = &rest[..end];
6294                        let local = &rest[end + 1..];
6295                        return Some(QName::new(
6296                            if ns.is_empty() { None } else { Some(ns) },
6297                            local,
6298                        ));
6299                    }
6300                }
6301                return Some(QName::new(None, rest));
6302            }
6303        }
6304    }
6305    None
6306}
6307
6308/// Union two attribute-wildcard namespace constraints (XSD §3.10.6,
6309/// "Attribute Wildcard Union").  The result accepts attributes from
6310/// the union of the two namespace sets.  `Any` is the absorbing
6311/// element; `Other` is conservatively widened to `Any` rather than
6312/// reasoning about which target namespace each one excludes.
6313/// `process_contents` falls through from the more-derived wildcard
6314/// since that's what the local declaration intended.
6315/// Combine an existing accumulated attribute wildcard with a newly
6316/// arrived one from another `<xs:attributeGroup>` contribution.
6317/// Intersect semantics per XSD §3.10.6 — but when one side is
6318/// `None` (no wildcard yet), the new one wins as-is.  Returning
6319/// `None` means no effective wildcard remains (intersection
6320/// reduced to the empty set).
6321fn merge_any_intersect(
6322    acc: Option<super::schema::Wildcard>,
6323    new: Option<super::schema::Wildcard>,
6324) -> Option<super::schema::Wildcard> {
6325    match (acc, new) {
6326        (None, x) | (x, None) => x,
6327        (Some(a), Some(b)) => intersect_wildcards(&a, &b),
6328    }
6329}
6330
6331/// XSD §3.10.6 "Attribute Wildcard Intersection" — used when a
6332/// single complex type composes multiple `<xs:anyAttribute>`s
6333/// (from its own body plus each referenced `<xs:attributeGroup>`).
6334/// The effective wildcard accepts only those attributes accepted
6335/// by EVERY contributor.
6336///
6337/// `Any ∩ X = X`.  Two enumerated lists intersect by set
6338/// intersection.  `##other` doesn't combine cleanly with another
6339/// `##other` from a different target namespace, but we collapse
6340/// conservatively to the spec's "absent" result (treat as None to
6341/// disable the wildcard).
6342fn intersect_wildcards(
6343    a: &super::schema::Wildcard, b: &super::schema::Wildcard,
6344) -> Option<super::schema::Wildcard> {
6345    use super::schema::{NamespaceConstraint, Wildcard};
6346    let namespaces = match (&a.namespaces, &b.namespaces) {
6347        (NamespaceConstraint::Any, x) | (x, NamespaceConstraint::Any) => x.clone(),
6348        (NamespaceConstraint::List(la), NamespaceConstraint::List(lb)) => {
6349            let common: Vec<Option<Arc<str>>> = la.iter()
6350                .filter(|x| lb.contains(x))
6351                .cloned()
6352                .collect();
6353            if common.is_empty() {
6354                // Empty intersection — no attributes are accepted.
6355                // Represent as an empty list (matches nothing).
6356                NamespaceConstraint::List(Vec::new())
6357            } else {
6358                NamespaceConstraint::List(common)
6359            }
6360        }
6361        (NamespaceConstraint::Other, NamespaceConstraint::List(lb))
6362        | (NamespaceConstraint::List(lb), NamespaceConstraint::Other) => {
6363            // `##other` excludes the schema's targetNamespace and
6364            // the absent namespace; intersect with explicit list by
6365            // dropping the absent entry.  We don't know the target
6366            // namespace here, so be conservative: keep all non-None
6367            // entries that *might* be in `##other`'s set.
6368            let kept: Vec<Option<Arc<str>>> = lb.iter()
6369                .filter(|x| x.is_some())
6370                .cloned()
6371                .collect();
6372            NamespaceConstraint::List(kept)
6373        }
6374        (NamespaceConstraint::Other, NamespaceConstraint::Other) => {
6375            NamespaceConstraint::Other
6376        }
6377    };
6378    // processContents intersection: choose the *stricter* of the two
6379    // (the result must satisfy both).  strict > lax > skip.
6380    let process_contents = match (a.process_contents, b.process_contents) {
6381        (x, y) if x == y => x,
6382        (super::schema::ProcessContents::Strict, _)
6383        | (_, super::schema::ProcessContents::Strict) =>
6384            super::schema::ProcessContents::Strict,
6385        (super::schema::ProcessContents::Lax, _)
6386        | (_, super::schema::ProcessContents::Lax) =>
6387            super::schema::ProcessContents::Lax,
6388        _ => super::schema::ProcessContents::Skip,
6389    };
6390    Some(Wildcard {
6391        namespaces,
6392        process_contents,
6393        not_qnames:                a.not_qnames.iter().chain(&b.not_qnames).cloned().collect(),
6394        not_namespaces:            a.not_namespaces.iter().chain(&b.not_namespaces).cloned().collect(),
6395        not_qname_defined:         a.not_qname_defined || b.not_qname_defined,
6396        not_qname_defined_sibling: a.not_qname_defined_sibling || b.not_qname_defined_sibling,
6397    })
6398}
6399
6400fn union_wildcards(a: &super::schema::Wildcard, b: &super::schema::Wildcard)
6401    -> super::schema::Wildcard
6402{
6403    use super::schema::{NamespaceConstraint, Wildcard};
6404    let namespaces = match (&a.namespaces, &b.namespaces) {
6405        (NamespaceConstraint::Any, _) | (_, NamespaceConstraint::Any) => NamespaceConstraint::Any,
6406        // ##other ∪ list-containing-None covers every namespace except
6407        // the target's — overapproximate to Any (we lack a "any but X"
6408        // constraint, and the practical effect is to accept namespaces
6409        // both sides allow).
6410        (NamespaceConstraint::Other, NamespaceConstraint::List(l))
6411        | (NamespaceConstraint::List(l), NamespaceConstraint::Other)
6412            if l.iter().any(|e| e.is_none()) =>
6413            NamespaceConstraint::Any,
6414        (NamespaceConstraint::Other, _) | (_, NamespaceConstraint::Other) => NamespaceConstraint::Other,
6415        (NamespaceConstraint::List(la), NamespaceConstraint::List(lb)) => {
6416            let mut combined: Vec<Option<Arc<str>>> = la.clone();
6417            for item in lb {
6418                if !combined.iter().any(|existing| existing == item) {
6419                    combined.push(item.clone());
6420                }
6421            }
6422            NamespaceConstraint::List(combined)
6423        }
6424    };
6425    // Wildcard composition for restriction: take the derived's `not*`
6426    // sets as-is (extension can only ADD to the exclusion list — XSD
6427    // 1.1 §3.10.4 requires the derived's notQName/notNamespace to be
6428    // a superset of the base's, which the schema author is
6429    // responsible for; we don't re-check here).
6430    Wildcard {
6431        namespaces,
6432        process_contents:          b.process_contents,
6433        not_qnames:                b.not_qnames.clone(),
6434        not_namespaces:            b.not_namespaces.clone(),
6435        not_qname_defined:         b.not_qname_defined,
6436        not_qname_defined_sibling: b.not_qname_defined_sibling,
6437    }
6438}
6439
6440/// Compose `derived = extension of base` per XSD §3.4.2.  Returns a
6441/// new `ComplexType` with merged content + attributes.
6442fn compose_extension(base: &ComplexType, derived: &ComplexType) -> ComplexType {
6443    use super::schema::{MaxOccurs, Term};
6444    let merged_content = match (&base.content, &derived.content) {
6445        (ContentModel::Empty, c) => c.clone(),
6446        (b, ContentModel::Empty) => b.clone(),
6447        (
6448            ContentModel::Complex { root: b_root, mixed: b_mixed },
6449            ContentModel::Complex { root: d_root, mixed: d_mixed },
6450        ) => ContentModel::Complex {
6451            root: Particle {
6452                min_occurs: 1,
6453                max_occurs: MaxOccurs::Bounded(1),
6454                term: Term::Group {
6455                    kind: GroupKind::Sequence,
6456                    particles: Arc::from(vec![b_root.clone(), d_root.clone()]),
6457                },
6458            },
6459            mixed: *b_mixed || *d_mixed,
6460        },
6461        // Simple-content extension of a simple base: derived's
6462        // simpleType wraps with new attrs; content stays simple.
6463        // Other mixed cases (simple base + complex derived, etc.)
6464        // are spec-disallowed; we pass derived's content through.
6465        (ContentModel::Simple(_), c) => c.clone(),
6466        (_, c) => c.clone(),
6467    };
6468
6469    // Attribute merge: base first, derived overrides same-name.
6470    let mut attrs: Vec<super::schema::AttributeUse> = base.attributes.clone();
6471    for d_au in &derived.attributes {
6472        let same_name = attrs.iter().position(|a| a.decl.name == d_au.decl.name);
6473        match same_name {
6474            Some(idx) => attrs[idx] = d_au.clone(),
6475            None      => attrs.push(d_au.clone()),
6476        }
6477    }
6478
6479    // Wildcard composition under extension (XSD §3.4.2): the resulting
6480    // `anyAttribute` is the *union* of the base's and derived's
6481    // wildcards.  Either one alone wins when the other is absent.
6482    let any_attribute = match (&base.any_attribute, &derived.any_attribute) {
6483        (Some(b), Some(d)) => Some(union_wildcards(b, d)),
6484        (Some(w), None) | (None, Some(w)) => Some(w.clone()),
6485        (None, None) => None,
6486    };
6487
6488    ComplexType {
6489        name:          derived.name.clone(),
6490        derivation:    derived.derivation.clone(),
6491        content:       merged_content,
6492        matcher:       std::sync::OnceLock::new(),
6493        attributes:    attrs,
6494        any_attribute,
6495        abstract_:     derived.abstract_,
6496        block:         derived.block,
6497        final_:        derived.final_,
6498        pending_attribute_group_refs: Vec::new(),
6499        // XSD 1.1: `xs:assert` declarations are *added to* the
6500        // assertions of the base type on extension; on restriction
6501        // they replace them (the restriction body re-declares any
6502        // assertions to keep).  This helper handles both flavours;
6503        // the caller fills `derived.assertions` accordingly upstream.
6504        assertions: derived.assertions.clone(),
6505    }
6506}
6507
6508/// Replace `<xs:element ref="X"/>` placeholders with the actual
6509/// top-level decl from `elements`.
6510///
6511/// The parser produces a stand-in `ElementDecl` (type=xs:string,
6512/// no nillable/abstract/etc.) for every `ref=`-form particle —
6513/// "patches in the real one" was promised but never wired.  Without
6514/// this pass, the validator gets the wrong decl when the DFA's
6515/// content matcher steps on a substitution-group head's name, and
6516/// downstream features (xsi:type derivation check, required-attribute
6517/// enforcement, nillable, identity constraints) all see the
6518/// placeholder's empty fields instead of the schema-author's
6519/// declaration.
6520///
6521/// Cost: one walk of each top-level complex type's content tree,
6522/// linear in particle count.  Reuses the same Particle/ContentModel
6523/// Clone derive added for extension merging.
6524/// Resolve every `ComplexType::pending_attribute_group_refs` entry
6525/// by expanding the referenced [`AttributeGroup`]'s attribute uses
6526/// (and anyAttribute) into the type, then clearing the pending list.
6527/// Walks both top-level types and inline-on-element types.  A pending
6528/// ref to an undeclared group is silently dropped (consistent with
6529/// how forward refs were treated before this pass).
6530/// Bottom-up flatten every attribute group's attribute list to
6531/// include the transitive union of attribute uses from every group
6532/// it references (XSD §3.6.3).  Iterates fixed-point so order of
6533/// declaration in source doesn't matter — `attg1` declared before
6534/// `attg2` still picks up attg2's attributes when attg1 references
6535/// it.  Cycle detection runs separately
6536/// ([`check_attribute_group_cycles`]) before this step, so the
6537/// iteration always terminates.  Wildcards merge by intersection
6538/// per §3.10.6 — same rule [`rewrite_complex_for_ag_refs`] applies
6539/// when the type itself references the group.
6540fn flatten_nested_attribute_group_refs(
6541    groups: &mut HashMap<QName, Arc<AttributeGroup>>,
6542    refs_by_owner: &HashMap<QName, Vec<QName>>,
6543) {
6544    let max_rounds = groups.len() + 1;
6545    for _ in 0..max_rounds {
6546        let mut changed = false;
6547        let names: Vec<QName> = groups.keys().cloned().collect();
6548        for name in names {
6549            let Some(refs) = refs_by_owner.get(&name) else { continue };
6550            if refs.is_empty() { continue; }
6551            let Some(current) = groups.get(&name).cloned() else { continue };
6552            let mut attrs = current.attributes.clone();
6553            let mut any   = current.any.clone();
6554            let mut grew  = false;
6555            for ref_qn in refs {
6556                let Some(target) = groups.get(ref_qn) else { continue };
6557                for au in &target.attributes {
6558                    // Skip refs already present so a re-flatten round
6559                    // doesn't multiply entries.  Identity is the
6560                    // declared name + use kind — `Arc::ptr_eq` would
6561                    // miss the merged copies produced upstream.
6562                    let already = attrs.iter().any(|existing|
6563                        existing.decl.name == au.decl.name
6564                        && existing.use_kind == au.use_kind);
6565                    if !already {
6566                        attrs.push(au.clone());
6567                        grew = true;
6568                    }
6569                }
6570                any = merge_any_intersect(any, target.any.clone());
6571            }
6572            if grew {
6573                let new_ag = AttributeGroup {
6574                    name:       current.name.clone(),
6575                    attributes: attrs,
6576                    any,
6577                };
6578                groups.insert(name, Arc::new(new_ag));
6579                changed = true;
6580            }
6581        }
6582        if !changed { break; }
6583    }
6584}
6585
6586fn resolve_attribute_group_refs(
6587    types:    &mut HashMap<QName, TypeRef>,
6588    elements: &mut HashMap<QName, Arc<ElementDecl>>,
6589    groups:   &HashMap<QName, Arc<AttributeGroup>>,
6590) -> Result<(), SchemaCompileError> {
6591    let names: Vec<QName> = types.keys().cloned().collect();
6592    for name in names {
6593        if let Some(TypeRef::Complex(ct)) = types.get(&name) {
6594            let rewritten = rewrite_complex_for_ag_refs(ct, groups);
6595            types.insert(name, TypeRef::Complex(Arc::new(rewritten)));
6596        }
6597    }
6598
6599    let elem_names: Vec<QName> = elements.keys().cloned().collect();
6600    for ename in elem_names {
6601        if let Some(decl) = elements.get(&ename) {
6602            if let TypeRef::Complex(ct) = &decl.type_def {
6603                let rewritten = rewrite_complex_for_ag_refs(ct, groups);
6604                let new_decl = Arc::new(ElementDecl {
6605                    name:               decl.name.clone(),
6606                    type_def:           TypeRef::Complex(Arc::new(rewritten)),
6607                    nillable:           decl.nillable,
6608                    default:            decl.default.clone(),
6609                    fixed:              decl.fixed.clone(),
6610                    abstract_:          decl.abstract_,
6611                    substitution_group: decl.substitution_group.clone(),
6612                    block:              decl.block,
6613                    final_:             decl.final_,
6614                    identity:           decl.identity.clone(),
6615                });
6616                elements.insert(ename, new_decl);
6617            }
6618        }
6619    }
6620    Ok(())
6621}
6622
6623/// Rebuild a ComplexType with all pending attributeGroup refs
6624/// expanded — both on the type itself and on every inline complex
6625/// type reachable through its content model's particle tree.
6626fn rewrite_complex_for_ag_refs(
6627    ct:     &ComplexType,
6628    groups: &HashMap<QName, Arc<AttributeGroup>>,
6629) -> ComplexType {
6630    let mut attributes = ct.attributes.clone();
6631    let mut any_attribute = ct.any_attribute.clone();
6632    for ref_qn in &ct.pending_attribute_group_refs {
6633        if let Some(ag) = groups.get(ref_qn) {
6634            for au in &ag.attributes {
6635                attributes.push(au.clone());
6636            }
6637            // XSD §3.10.6 — multiple wildcards in one complex
6638            // type INTERSECT to form the effective `anyAttribute`.
6639            any_attribute = merge_any_intersect(any_attribute, ag.any.clone());
6640        }
6641    }
6642    let new_content = match &ct.content {
6643        ContentModel::Complex { root, mixed } => {
6644            ContentModel::Complex {
6645                root: rewrite_particle_for_ag_refs(root.clone(), groups),
6646                mixed: *mixed,
6647            }
6648        }
6649        other => other.clone(),
6650    };
6651    // Snapshot-captured base types (used by `<xs:redefine>` to point
6652    // at the pre-redefinition version) may carry pending refs of
6653    // their own.  Recurse so the redefinition sees the original's
6654    // attribute set after expansion against the *current* group set.
6655    let new_derivation = ct.derivation.as_ref().map(|d| {
6656        let new_base = match &d.base {
6657            TypeRef::Complex(c) => TypeRef::Complex(Arc::new(rewrite_complex_for_ag_refs(c, groups))),
6658            other => other.clone(),
6659        };
6660        super::types::Derivation { method: d.method, base: new_base }
6661    });
6662    ComplexType {
6663        name:          ct.name.clone(),
6664        derivation:    new_derivation,
6665        content:       new_content,
6666        matcher:       std::sync::OnceLock::new(),
6667        attributes,
6668        any_attribute,
6669        abstract_:     ct.abstract_,
6670        block:         ct.block,
6671        final_:        ct.final_,
6672        pending_attribute_group_refs: Vec::new(),
6673        assertions: ct.assertions.clone(),
6674    }
6675}
6676
6677fn rewrite_particle_for_ag_refs(
6678    p:      Particle,
6679    groups: &HashMap<QName, Arc<AttributeGroup>>,
6680) -> Particle {
6681    let term = match p.term {
6682        Term::Element(decl) => {
6683            // Descend into the element's type_def if it's complex —
6684            // an inline anonymous complex type might carry pending
6685            // attribute group refs of its own.
6686            let new_decl = if let TypeRef::Complex(ct) = &decl.type_def {
6687                let new_ct = rewrite_complex_for_ag_refs(ct, groups);
6688                Arc::new(ElementDecl {
6689                    name:               decl.name.clone(),
6690                    type_def:           TypeRef::Complex(Arc::new(new_ct)),
6691                    nillable:           decl.nillable,
6692                    default:            decl.default.clone(),
6693                    fixed:              decl.fixed.clone(),
6694                    abstract_:          decl.abstract_,
6695                    substitution_group: decl.substitution_group.clone(),
6696                    block:              decl.block,
6697                    final_:             decl.final_,
6698                    identity:           decl.identity.clone(),
6699                })
6700            } else {
6701                decl
6702            };
6703            Term::Element(new_decl)
6704        }
6705        Term::Group { kind, particles } => {
6706            let new_particles: Vec<Particle> = particles.iter()
6707                .cloned()
6708                .map(|p| rewrite_particle_for_ag_refs(p, groups))
6709                .collect();
6710            Term::Group { kind, particles: Arc::from(new_particles) }
6711        }
6712        other => other,
6713    };
6714    Particle { min_occurs: p.min_occurs, max_occurs: p.max_occurs, term }
6715}
6716
6717/// Replace every `Term::GroupRef(name)` in the schema with the
6718/// referenced [`ModelGroup`]'s particle, walking every complex
6719/// type's content tree (top-level + inline-on-elements).
6720///
6721/// Group refs can chain — a group can reference another group — so
6722/// each Term::GroupRef is expanded by walking the referent's
6723/// particle, which in turn is walked recursively (with a depth
6724/// bound to break cycles).
6725/// XSD §3.8.6 src-model-group — a model group definition is invalid
6726/// when it (transitively) references itself without crossing an
6727/// element boundary.  Element-mediated recursion (Container element
6728/// whose type contains Container) is fine because each level is a new
6729/// instance; pure group-to-group recursion is structurally infinite.
6730fn check_model_group_cycles(
6731    groups: &HashMap<QName, Arc<ModelGroup>>,
6732    redefined_groups: &HashSet<QName>,
6733) -> Result<(), SchemaCompileError> {
6734    fn walk(
6735        p: &Particle,
6736        groups: &HashMap<QName, Arc<ModelGroup>>,
6737        active: &mut Vec<QName>,
6738        redefined: &HashSet<QName>,
6739    ) -> Result<(), SchemaCompileError> {
6740        match &p.term {
6741            Term::GroupRef(name) => {
6742                if active.contains(name) {
6743                    return Err(SchemaCompileError::msg(format!(
6744                        "<xs:group name={:?}>: model-group definition is circular \
6745                         — references itself without an intervening element \
6746                         declaration (XSD §3.8.6 src-model-group)",
6747                        name.local,
6748                    )));
6749                }
6750                let Some(g) = groups.get(name) else { return Ok(()); };
6751                active.push(name.clone());
6752                walk(&g.particle, groups, active, redefined)?;
6753                active.pop();
6754            }
6755            Term::Group { particles, .. } => {
6756                for p in particles.iter() { walk(p, groups, active, redefined)?; }
6757            }
6758            // Element boundary — the cycle wouldn't be a pure group
6759            // cycle from here on. Element decls live in their own type
6760            // expansion universe; nothing to check at this layer.
6761            Term::Element(_) | Term::Wildcard(_) => {}
6762        }
6763        Ok(())
6764    }
6765    for (name, g) in groups {
6766        // Groups originating in <xs:redefine> contain at most one
6767        // self-reference per src-redefine; that's a legal pattern,
6768        // not a structural cycle. Skip them.
6769        if redefined_groups.contains(name) { continue; }
6770        let mut active = vec![name.clone()];
6771        walk(&g.particle, groups, &mut active, redefined_groups)?;
6772    }
6773    Ok(())
6774}
6775
6776fn resolve_group_refs(
6777    types:    &mut HashMap<QName, TypeRef>,
6778    elements: &mut HashMap<QName, Arc<ElementDecl>>,
6779    groups:   &HashMap<QName, Arc<ModelGroup>>,
6780) -> Result<(), SchemaCompileError> {
6781    let names: Vec<QName> = types.keys().cloned().collect();
6782    for name in names {
6783        if let Some(TypeRef::Complex(ct)) = types.get(&name) {
6784            if !content_has_group_ref(&ct.content) { continue; }
6785            let new_content = expand_group_refs_in_content(&ct.content, groups, 0)?;
6786            let new_ct = ComplexType {
6787                name:          ct.name.clone(),
6788                derivation:    ct.derivation.clone(),
6789                content:       new_content,
6790                matcher:       std::sync::OnceLock::new(),
6791                attributes:    ct.attributes.clone(),
6792                any_attribute: ct.any_attribute.clone(),
6793                abstract_:     ct.abstract_,
6794                block:         ct.block,
6795                final_:        ct.final_,
6796                pending_attribute_group_refs: ct.pending_attribute_group_refs.clone(),
6797                assertions: ct.assertions.clone(),
6798            };
6799            types.insert(name, TypeRef::Complex(Arc::new(new_ct)));
6800        }
6801    }
6802
6803    let elem_names: Vec<QName> = elements.keys().cloned().collect();
6804    for ename in elem_names {
6805        if let Some(decl) = elements.get(&ename) {
6806            if let TypeRef::Complex(ct) = &decl.type_def {
6807                if !content_has_group_ref(&ct.content) { continue; }
6808                let new_content = expand_group_refs_in_content(&ct.content, groups, 0)?;
6809                let new_ct = ComplexType {
6810                    name:          ct.name.clone(),
6811                    derivation:    ct.derivation.clone(),
6812                    content:       new_content,
6813                    matcher:       std::sync::OnceLock::new(),
6814                    attributes:    ct.attributes.clone(),
6815                    any_attribute: ct.any_attribute.clone(),
6816                    abstract_:     ct.abstract_,
6817                    block:         ct.block,
6818                    final_:        ct.final_,
6819                    pending_attribute_group_refs: ct.pending_attribute_group_refs.clone(),
6820                    assertions: ct.assertions.clone(),
6821                };
6822                let new_decl = Arc::new(ElementDecl {
6823                    name:               decl.name.clone(),
6824                    type_def:           TypeRef::Complex(Arc::new(new_ct)),
6825                    nillable:           decl.nillable,
6826                    default:            decl.default.clone(),
6827                    fixed:              decl.fixed.clone(),
6828                    abstract_:          decl.abstract_,
6829                    substitution_group: decl.substitution_group.clone(),
6830                    block:              decl.block,
6831                    final_:             decl.final_,
6832                    identity:           decl.identity.clone(),
6833                });
6834                elements.insert(ename, new_decl);
6835            }
6836        }
6837    }
6838    Ok(())
6839}
6840
6841fn content_has_group_ref(c: &ContentModel) -> bool {
6842    match c {
6843        ContentModel::Complex { root, .. } => particle_has_group_ref(root),
6844        _ => false,
6845    }
6846}
6847
6848fn particle_has_group_ref(p: &Particle) -> bool {
6849    match &p.term {
6850        Term::GroupRef(_) => true,
6851        Term::Group { particles, .. } => particles.iter().any(particle_has_group_ref),
6852        _ => false,
6853    }
6854}
6855
6856const MAX_GROUP_DEPTH: usize = 64;
6857
6858fn expand_group_refs_in_content(
6859    c:      &ContentModel,
6860    groups: &HashMap<QName, Arc<ModelGroup>>,
6861    depth:  usize,
6862) -> Result<ContentModel, SchemaCompileError> {
6863    expand_group_refs_in_content_inner(c, groups, depth, &mut HashSet::new())
6864}
6865
6866fn expand_group_refs_in_content_inner(
6867    c:      &ContentModel,
6868    groups: &HashMap<QName, Arc<ModelGroup>>,
6869    depth:  usize,
6870    active: &mut HashSet<QName>,
6871) -> Result<ContentModel, SchemaCompileError> {
6872    match c {
6873        ContentModel::Complex { root, mixed } => {
6874            let root = expand_group_refs_in_particle_inner(
6875                root.clone(), groups, depth, active,
6876            )?;
6877            Ok(ContentModel::Complex { root, mixed: *mixed })
6878        }
6879        other => Ok(other.clone()),
6880    }
6881}
6882
6883#[allow(dead_code)]
6884fn expand_group_refs_in_particle(
6885    p:      Particle,
6886    groups: &HashMap<QName, Arc<ModelGroup>>,
6887    depth:  usize,
6888) -> Result<Particle, SchemaCompileError> {
6889    expand_group_refs_in_particle_inner(p, groups, depth, &mut HashSet::new())
6890}
6891
6892/// Same as [`expand_group_refs_in_particle`] but threads an "active"
6893/// set of group names currently mid-expansion.  Recursive group refs
6894/// guarded by an element decl (XSD §3.7.6 — the cycle resolves at
6895/// runtime through the element boundary) are left as `Term::GroupRef`
6896/// rather than blowing the depth limit during expansion; later
6897/// matcher builds will re-encounter them inside the element's own
6898/// type and expand them with a fresh set.
6899fn expand_group_refs_in_particle_inner(
6900    p:      Particle,
6901    groups: &HashMap<QName, Arc<ModelGroup>>,
6902    depth:  usize,
6903    active: &mut HashSet<QName>,
6904) -> Result<Particle, SchemaCompileError> {
6905    if depth > MAX_GROUP_DEPTH {
6906        return Err(SchemaCompileError::msg(
6907            "model group reference chain exceeds depth limit (cycle?)"
6908        ));
6909    }
6910    let term = match p.term {
6911        Term::GroupRef(ref name) => {
6912            if active.contains(name) {
6913                // Cycle through element-boundary recursion (see test
6914                // addB077); leave the ref intact for the element's
6915                // own matcher to expand on its independent pass.
6916                return Ok(Particle {
6917                    min_occurs: p.min_occurs,
6918                    max_occurs: p.max_occurs,
6919                    term:       Term::GroupRef(name.clone()),
6920                });
6921            }
6922            let g = groups.get(name).ok_or_else(|| SchemaCompileError::msg(
6923                format!("<xs:group ref={name}> refers to an undeclared group")
6924            ))?;
6925            // XSD §3.7.6 (cos-all-limited) — a `<xs:group ref="G"/>`
6926            // whose target group is an `<xs:all>` must have
6927            // minOccurs ∈ {0, 1} and maxOccurs = 1.
6928            if matches!(g.particle.term, Term::Group { kind: GroupKind::All, .. })
6929                && (p.min_occurs > 1 || p.max_occurs != MaxOccurs::Bounded(1))
6930            {
6931                return Err(SchemaCompileError::msg(format!(
6932                    "<xs:group ref={name}>: a reference to an <xs:all> group must \
6933                     have minOccurs ∈ {{0,1}} and maxOccurs=1 (XSD §3.7.6 cos-all-limited)"
6934                )));
6935            }
6936            active.insert(name.clone());
6937            let expanded = expand_group_refs_in_particle_inner(
6938                g.particle.clone(), groups, depth + 1, active,
6939            )?;
6940            active.remove(name);
6941            expanded.term
6942        }
6943        Term::Group { kind, particles } => {
6944            let new_particles: Vec<Particle> = particles.iter()
6945                .cloned()
6946                .map(|p| expand_group_refs_in_particle_inner(p, groups, depth + 1, active))
6947                .collect::<Result<_, _>>()?;
6948            Term::Group { kind, particles: Arc::from(new_particles) }
6949        }
6950        Term::Element(decl) => {
6951            // Element refs into an inline complex type that itself
6952            // contains `<xs:group ref="…">`.  Recurse with the same
6953            // active set so cycles via the element body terminate
6954            // (leaving the GroupRef in place for the element type's
6955            // own matcher build to resolve later).
6956            Term::Element(expand_group_refs_in_element_decl_inner(
6957                decl, groups, depth + 1, active,
6958            )?)
6959        }
6960        other => other,
6961    };
6962    Ok(Particle { min_occurs: p.min_occurs, max_occurs: p.max_occurs, term })
6963}
6964
6965/// Walk an element declaration's inline complex type (if any) and
6966/// resolve every `Term::GroupRef` inside it.  Returns the original Arc
6967/// untouched when nothing changes so identity comparisons elsewhere
6968/// stay cheap.
6969#[allow(dead_code)]
6970fn expand_group_refs_in_element_decl(
6971    decl:   Arc<ElementDecl>,
6972    groups: &HashMap<QName, Arc<ModelGroup>>,
6973    depth:  usize,
6974) -> Result<Arc<ElementDecl>, SchemaCompileError> {
6975    expand_group_refs_in_element_decl_inner(decl, groups, depth, &mut HashSet::new())
6976}
6977
6978fn expand_group_refs_in_element_decl_inner(
6979    decl:   Arc<ElementDecl>,
6980    groups: &HashMap<QName, Arc<ModelGroup>>,
6981    depth:  usize,
6982    active: &mut HashSet<QName>,
6983) -> Result<Arc<ElementDecl>, SchemaCompileError> {
6984    let TypeRef::Complex(ct) = &decl.type_def else { return Ok(decl); };
6985    if !content_has_group_ref(&ct.content) { return Ok(decl); }
6986    let new_content = expand_group_refs_in_content_inner(&ct.content, groups, depth, active)?;
6987    let new_ct = ComplexType {
6988        name:                         ct.name.clone(),
6989        derivation:                   ct.derivation.clone(),
6990        content:                      new_content,
6991        matcher:                      std::sync::OnceLock::new(),
6992        attributes:                   ct.attributes.clone(),
6993        any_attribute:                ct.any_attribute.clone(),
6994        abstract_:                    ct.abstract_,
6995        block:                        ct.block,
6996        final_:                       ct.final_,
6997        pending_attribute_group_refs: ct.pending_attribute_group_refs.clone(),
6998        assertions: ct.assertions.clone(),
6999    };
7000    Ok(Arc::new(ElementDecl {
7001        name:               decl.name.clone(),
7002        type_def:           TypeRef::Complex(Arc::new(new_ct)),
7003        nillable:           decl.nillable,
7004        default:            decl.default.clone(),
7005        fixed:              decl.fixed.clone(),
7006        abstract_:          decl.abstract_,
7007        substitution_group: decl.substitution_group.clone(),
7008        block:              decl.block,
7009        final_:             decl.final_,
7010        identity:           decl.identity.clone(),
7011    }))
7012}
7013
7014fn resolve_element_refs(
7015    types:    &mut HashMap<QName, TypeRef>,
7016    elements: &mut HashMap<QName, Arc<ElementDecl>>,
7017) {
7018    // Snapshot the un-patched elements so iteration uses a stable
7019    // lookup while we mutate the live maps.
7020    let snapshot: HashMap<QName, Arc<ElementDecl>> = elements.clone();
7021
7022    let names: Vec<QName> = types.keys().cloned().collect();
7023    for name in names {
7024        let needs_rewrite = match types.get(&name) {
7025            Some(TypeRef::Complex(ct)) => match &ct.content {
7026                ContentModel::Complex { root, .. } => particle_has_unresolved_ref(root, &snapshot),
7027                _ => false,
7028            },
7029            _ => false,
7030        };
7031        if !needs_rewrite { continue; }
7032        if let Some(TypeRef::Complex(ct)) = types.get(&name) {
7033            types.insert(name, TypeRef::Complex(Arc::new(rewrite_complex_type(ct, &snapshot))));
7034        }
7035    }
7036
7037    // Anonymous inline complex types attached to top-level element
7038    // decls also need patching — they don't live in `types`.
7039    let elem_names: Vec<QName> = elements.keys().cloned().collect();
7040    for ename in elem_names {
7041        let needs_rewrite = match elements.get(&ename) {
7042            Some(decl) => match &decl.type_def {
7043                TypeRef::Complex(ct) => match &ct.content {
7044                    ContentModel::Complex { root, .. } => particle_has_unresolved_ref(root, &snapshot),
7045                    _ => false,
7046                },
7047                _ => false,
7048            },
7049            None => false,
7050        };
7051        if !needs_rewrite { continue; }
7052        if let Some(decl) = elements.get(&ename) {
7053            if let TypeRef::Complex(ct) = &decl.type_def {
7054                let new_ct = rewrite_complex_type(ct, &snapshot);
7055                let new_decl = Arc::new(ElementDecl {
7056                    name:               decl.name.clone(),
7057                    type_def:           TypeRef::Complex(Arc::new(new_ct)),
7058                    nillable:           decl.nillable,
7059                    default:            decl.default.clone(),
7060                    fixed:              decl.fixed.clone(),
7061                    abstract_:          decl.abstract_,
7062                    substitution_group: decl.substitution_group.clone(),
7063                    block:              decl.block,
7064                    final_:             decl.final_,
7065                    identity:           decl.identity.clone(),
7066                });
7067                elements.insert(ename, new_decl);
7068            }
7069        }
7070    }
7071}
7072
7073/// Build a fresh ComplexType with placeholder refs in its content
7074/// model replaced by the real ElementDecl from `elements`.  Returns
7075/// a structurally-equivalent type — only the Element terms in the
7076/// content tree are swapped.  The matcher is reset to OnceLock::new()
7077/// so it gets rebuilt over the patched content.
7078fn rewrite_complex_type(
7079    ct:       &ComplexType,
7080    elements: &HashMap<QName, Arc<ElementDecl>>,
7081) -> ComplexType {
7082    let new_content = match &ct.content {
7083        ContentModel::Complex { root, mixed } => {
7084            let rewritten = rewrite_particle_refs(root.clone(), elements);
7085            ContentModel::Complex { root: rewritten, mixed: *mixed }
7086        }
7087        other => other.clone(),
7088    };
7089    ComplexType {
7090        name:          ct.name.clone(),
7091        derivation:    ct.derivation.clone(),
7092        content:       new_content,
7093        matcher:       std::sync::OnceLock::new(),
7094        attributes:    ct.attributes.clone(),
7095        any_attribute: ct.any_attribute.clone(),
7096        abstract_:     ct.abstract_,
7097        block:         ct.block,
7098        final_:        ct.final_,
7099        pending_attribute_group_refs: ct.pending_attribute_group_refs.clone(),
7100        assertions: ct.assertions.clone(),
7101    }
7102}
7103
7104fn particle_has_unresolved_ref(
7105    p: &Particle,
7106    elements: &HashMap<QName, Arc<ElementDecl>>,
7107) -> bool {
7108    match &p.term {
7109        Term::Element(decl) => {
7110            // A placeholder produced by `parse_local_element_particle`
7111            // has type=xs:string + no nillable + no abstract.  If a
7112            // real decl exists in `elements` for this name AND it
7113            // differs (by Arc identity), this particle needs rewrite.
7114            elements.get(&decl.name)
7115                .map(|real| !Arc::ptr_eq(real, decl))
7116                .unwrap_or(false)
7117        }
7118        Term::Group { particles, .. } => {
7119            particles.iter().any(|p| particle_has_unresolved_ref(p, elements))
7120        }
7121        Term::Wildcard(_) => false,
7122        Term::GroupRef(_) => false,
7123    }
7124}
7125
7126fn rewrite_particle_refs(
7127    p: Particle,
7128    elements: &HashMap<QName, Arc<ElementDecl>>,
7129) -> Particle {
7130    let term = match p.term {
7131        Term::Element(decl) => {
7132            let real = elements.get(&decl.name).cloned().unwrap_or(decl);
7133            Term::Element(real)
7134        }
7135        Term::Group { kind, particles } => {
7136            let new_particles: Vec<Particle> = particles.iter()
7137                .cloned()
7138                .map(|p| rewrite_particle_refs(p, elements))
7139                .collect();
7140            Term::Group { kind, particles: Arc::from(new_particles) }
7141        }
7142        Term::Wildcard(w) => Term::Wildcard(w),
7143        Term::GroupRef(name) => Term::GroupRef(name),
7144    };
7145    Particle { min_occurs: p.min_occurs, max_occurs: p.max_occurs, term }
7146}
7147
7148/// nested complex type reachable through its content model.  Uses a
7149/// pointer-set to avoid revisiting (and to break any Arc-based cycle,
7150/// though XSD types don't normally cycle at the Arc level).
7151fn walk_complex_for_matchers(
7152    ct: &Arc<ComplexType>,
7153    subs: &HashMap<QName, Vec<Arc<ElementDecl>>>,
7154    types: &HashMap<QName, TypeRef>,
7155    visited: &mut HashSet<usize>,
7156    target_ns: Option<&str>,
7157) -> Result<(), SchemaCompileError> {
7158    let ptr = Arc::as_ptr(ct) as usize;
7159    if !visited.insert(ptr) { return Ok(()); }
7160
7161    let matcher = super::dfa::build_matcher_with_target_ns(&ct.content, subs, types, target_ns)?;
7162    let _ = ct.matcher.set(matcher);
7163
7164    if let ContentModel::Complex { root, .. } = &ct.content {
7165        walk_particle_for_matchers(root, subs, types, visited, target_ns)?;
7166    }
7167    Ok(())
7168}
7169
7170fn walk_particle_for_matchers(
7171    p: &Particle,
7172    subs: &HashMap<QName, Vec<Arc<ElementDecl>>>,
7173    types: &HashMap<QName, TypeRef>,
7174    visited: &mut HashSet<usize>,
7175    target_ns: Option<&str>,
7176) -> Result<(), SchemaCompileError> {
7177    match &p.term {
7178        Term::Element(decl) => {
7179            if let TypeRef::Complex(ct) = &decl.type_def {
7180                walk_complex_for_matchers(ct, subs, types, visited, target_ns)?;
7181            }
7182        }
7183        Term::Group { particles, .. } => {
7184            for p in particles.iter() {
7185                walk_particle_for_matchers(p, subs, types, visited, target_ns)?;
7186            }
7187        }
7188        Term::Wildcard(_) => {}
7189        Term::GroupRef(_) => {
7190            // Cyclic group ref left intentionally by
7191            // `resolve_group_refs` (cycle crosses an element
7192            // boundary).  The DFA builder converts it to a
7193            // permissive wildcard; nothing to walk here.
7194        }
7195    }
7196    Ok(())
7197}
7198
7199fn parse_max_occurs(s: Option<&str>) -> Result<MaxOccurs, SchemaCompileError> {
7200    match s {
7201        None              => Ok(MaxOccurs::Bounded(1)),
7202        Some("unbounded") => Ok(MaxOccurs::Unbounded),
7203        Some(raw)         => parse_occurs_int(raw, "maxOccurs").map(|n| match n {
7204            // XSD spec defines maxOccurs as "non-negative integer"
7205            // with no upper bound. Values exceeding u32::MAX are
7206            // semantically Unbounded — they're still well-formed,
7207            // they just exceed what our matcher can count.
7208            Some(n) => MaxOccurs::Bounded(n),
7209            None    => MaxOccurs::Unbounded,
7210        }),
7211    }
7212}
7213
7214fn parse_min_occurs(s: Option<&str>) -> Result<u32, SchemaCompileError> {
7215    match s {
7216        None      => Ok(1),
7217        Some(raw) => parse_occurs_int(raw, "minOccurs")?.ok_or_else(|| {
7218            // minOccurs over u32::MAX is theoretically valid but
7219            // makes the matcher unreachable; treat it as a hard
7220            // schema-compile error.
7221            SchemaCompileError::msg(format!(
7222                "minOccurs={raw:?} exceeds the maximum supported value"
7223            ))
7224        }),
7225    }
7226}
7227
7228/// Parse an XSD non-negative integer occurrence count. Returns
7229/// `Some(n)` for u32-fitting values, `None` for valid but
7230/// u32-overflowing values, or an error for malformed input.
7231fn parse_occurs_int(raw: &str, attr: &str) -> Result<Option<u32>, SchemaCompileError> {
7232    if raw.is_empty() || raw.bytes().any(|b| !b.is_ascii_digit()) {
7233        return Err(SchemaCompileError::msg(format!(
7234            "{attr}={raw:?} is not a non-negative integer or 'unbounded'"
7235        )));
7236    }
7237    match raw.parse::<u32>() {
7238        Ok(n)  => Ok(Some(n)),
7239        Err(_) => Ok(None),
7240    }
7241}
7242
7243fn check_occurs(min: u32, max: MaxOccurs) -> Result<(), SchemaCompileError> {
7244    if let MaxOccurs::Bounded(m) = max {
7245        if min > m {
7246            return Err(SchemaCompileError::msg(format!(
7247                "minOccurs ({min}) > maxOccurs ({m})"
7248            )));
7249        }
7250    }
7251    Ok(())
7252}
7253
7254/// Remove inherited bounds that the derived restriction has
7255/// replaced with the opposite inclusivity.  XSD §4.3.9 says
7256/// `minInclusive` and `minExclusive` are mutually exclusive
7257/// within a single facet set — but during restriction the
7258/// derived type's explicit bound naturally supersedes the
7259/// inherited one.  Without this pruning the combined set carries
7260/// both and `validate_facet_set` mis-reports a conflict.
7261fn prune_replaced_bounds(facets: &mut FacetSet, base_count: usize) {
7262    let derived_has_min_incl = facets.facets[base_count..].iter()
7263        .any(|f| matches!(f, Facet::MinInclusive(_)));
7264    let derived_has_min_excl = facets.facets[base_count..].iter()
7265        .any(|f| matches!(f, Facet::MinExclusive(_)));
7266    let derived_has_max_incl = facets.facets[base_count..].iter()
7267        .any(|f| matches!(f, Facet::MaxInclusive(_)));
7268    let derived_has_max_excl = facets.facets[base_count..].iter()
7269        .any(|f| matches!(f, Facet::MaxExclusive(_)));
7270    let derived_has_length = facets.facets[base_count..].iter()
7271        .any(|f| matches!(f, Facet::Length(_)));
7272    let derived_has_minmax_length = facets.facets[base_count..].iter()
7273        .any(|f| matches!(f, Facet::MinLength(_) | Facet::MaxLength(_)));
7274
7275    let mut i = 0;
7276    facets.facets.retain(|f| {
7277        let inherited = i < base_count;
7278        i += 1;
7279        if !inherited { return true; }
7280        // Derived's explicit bound replaces the inherited
7281        // counterpart; the same goes for length-vs-min/max-length.
7282        let drop = matches!(
7283            (f, derived_has_min_incl, derived_has_min_excl,
7284                derived_has_max_incl, derived_has_max_excl,
7285                derived_has_length, derived_has_minmax_length),
7286            (Facet::MinExclusive(_), true,  _,    _,    _,    _, _) |
7287            (Facet::MinInclusive(_), _,    true, _,    _,    _, _) |
7288            (Facet::MaxExclusive(_), _,    _,    true, _,    _, _) |
7289            (Facet::MaxInclusive(_), _,    _,    _,    true, _, _) |
7290            (Facet::Length(_),       _,    _,    _,    _,    _, true) |
7291            (Facet::MinLength(_),    _,    _,    _,    _,    true, _) |
7292            (Facet::MaxLength(_),    _,    _,    _,    _,    true, _)
7293        );
7294        !drop
7295    });
7296}
7297
7298fn parse_block_set(s: Option<&str>) -> Result<BlockSet, SchemaCompileError> {
7299    let Some(s) = s else { return Ok(BlockSet::default()); };
7300    if s == "#all" {
7301        return Ok(BlockSet::all());
7302    }
7303    let mut out = BlockSet::default();
7304    for tok in s.split_whitespace() {
7305        match tok {
7306            "restriction"  => out |= BlockSet::RESTRICTION,
7307            "extension"    => out |= BlockSet::EXTENSION,
7308            "substitution" => out |= BlockSet::SUBSTITUTION,
7309            "list"         => out |= BlockSet::LIST,
7310            "union"        => out |= BlockSet::UNION,
7311            other => return Err(SchemaCompileError::msg(format!(
7312                "block/final attribute: {other:?} is not a valid token \
7313                 (expected 'restriction', 'extension', 'substitution', \
7314                 'list', 'union', or '#all')"
7315            ))),
7316        }
7317    }
7318    Ok(out)
7319}
7320
7321/// XSD §3.4.2 — `block` / `final` on `<xs:complexType>` accept only
7322/// `restriction`, `extension`, or `#all` (substitution belongs to
7323/// elements). Strictly validate the value, rejecting unknown or
7324/// element-only tokens.
7325/// XSD §3.3.2 — `block` on `<xs:element>` accepts only
7326/// `restriction`, `extension`, `substitution`, or `#all`.  The
7327/// simple-type tokens (`list`, `union`) are rejected.
7328fn parse_element_block_set(s: Option<&str>) -> Result<BlockSet, SchemaCompileError> {
7329    let Some(s) = s else { return Ok(BlockSet::default()); };
7330    if s == "#all" {
7331        return Ok(BlockSet::RESTRICTION | BlockSet::EXTENSION | BlockSet::SUBSTITUTION);
7332    }
7333    let mut out = BlockSet::default();
7334    for tok in s.split_whitespace() {
7335        match tok {
7336            "restriction"  => out |= BlockSet::RESTRICTION,
7337            "extension"    => out |= BlockSet::EXTENSION,
7338            "substitution" => out |= BlockSet::SUBSTITUTION,
7339            other          => return Err(SchemaCompileError::msg(format!(
7340                "<xs:element block={s:?}>: {other:?} is not a valid value \
7341                 (expected 'restriction', 'extension', 'substitution', or '#all')"
7342            ))),
7343        }
7344    }
7345    Ok(out)
7346}
7347
7348fn parse_ct_derivation_set(
7349    s: Option<&str>, attr: &str,
7350) -> Result<BlockSet, SchemaCompileError> {
7351    let Some(s) = s else { return Ok(BlockSet::default()); };
7352    if s == "#all" {
7353        return Ok(BlockSet::all());
7354    }
7355    let mut out = BlockSet::default();
7356    for tok in s.split_whitespace() {
7357        match tok {
7358            "restriction" => out |= BlockSet::RESTRICTION,
7359            "extension"   => out |= BlockSet::EXTENSION,
7360            other         => return Err(SchemaCompileError::msg(format!(
7361                "<xs:complexType {attr}={s:?}>: {other:?} is not a valid value \
7362                 (expected 'restriction', 'extension', or '#all')"
7363            ))),
7364        }
7365    }
7366    Ok(out)
7367}
7368
7369/// Reject `minOccurs` / `maxOccurs` on an `<xs:anyAttribute>` —
7370/// XSD §3.10.2's grammar admits them only on the element-wildcard
7371/// `<xs:any>`.  Call before [`parse_wildcard`].
7372fn check_no_occurs(attrs: &[Attr], element: &str) -> Result<(), SchemaCompileError> {
7373    for a in attrs {
7374        if matches!(a.name(), "minOccurs" | "maxOccurs") {
7375            return Err(SchemaCompileError::msg(format!(
7376                "<xs:{element} {}=...> is not allowed (only <xs:any> takes \
7377                 minOccurs / maxOccurs)",
7378                a.name(),
7379            )));
7380        }
7381    }
7382    Ok(())
7383}
7384
7385fn parse_wildcard_attrs(
7386    attrs: &[Attr],
7387    target_ns: &Option<Arc<str>>,
7388    version: SchemaVersion,
7389    parse_qname: &mut dyn FnMut(&str) -> Result<QName, SchemaCompileError>,
7390) -> Result<Wildcard, SchemaCompileError> {
7391    // XSD 1.1 added `notQName` and `notNamespace` to the wildcard
7392    // attribute set (§3.10.2).  Silently accepting them in 1.0 mode
7393    // would be the worst possible behaviour: the schema author thinks
7394    // they constrained the wildcard, but no constraint is enforced
7395    // at validation.  Reject explicitly in 1.0 mode; parse + enforce
7396    // in 1.1 mode.  `Auto` behaves like strict 1.0 until
7397    // `parse_schema` sees `vc:minVersion="1.1"` and promotes
7398    // `effective_version` to `Xsd11`.
7399    let is_xsd11 = matches!(version, SchemaVersion::Xsd11);
7400    if !is_xsd11 {
7401        if let Some(a) = attrs.iter().find(|a| a.name() == "notQName" || a.name() == "notNamespace") {
7402            return Err(SchemaCompileError::msg(format!(
7403                "wildcard attribute {:?} is XSD 1.1 only — \
7404                 set SchemaOptions::version to Xsd11, or to Auto with \
7405                 vc:minVersion=\"1.1\" on <xs:schema>",
7406                a.name(),
7407            )));
7408        }
7409    }
7410    // Parse `notQName` (1.1).  Tokens are either a QName
7411    // (`prefix:local` / `local`) or one of the special markers
7412    // `##defined` / `##definedSibling`.  The two markers need
7413    // schema-context resolution (the live element/attribute-decl
7414    // set, plus sibling info from the enclosing complex type) that
7415    // the wildcard parser doesn't have access to here; flag them
7416    // on the [`Wildcard`] for the validator to consume at match
7417    // time.
7418    let mut not_qnames: Vec<QName> = Vec::new();
7419    let mut not_qname_defined         = false;
7420    let mut not_qname_defined_sibling = false;
7421    if is_xsd11 {
7422        if let Some(a) = attrs.iter().find(|a| a.name() == "notQName") {
7423            for tok in a.value.as_ref().split_whitespace() {
7424                match tok {
7425                    "##defined"        => not_qname_defined = true,
7426                    "##definedSibling" => not_qname_defined_sibling = true,
7427                    qn                 => not_qnames.push(parse_qname(qn)?),
7428                }
7429            }
7430        }
7431    }
7432    // Parse `notNamespace` (1.1).  Same token set as `namespace`
7433    // minus `##any` / `##other` (those are only valid as the sole
7434    // value, not in a list).
7435    let mut not_namespaces: Vec<Option<Arc<str>>> = Vec::new();
7436    if is_xsd11 {
7437        if let Some(a) = attrs.iter().find(|a| a.name() == "notNamespace") {
7438            for tok in a.value.as_ref().split_whitespace() {
7439                match tok {
7440                    "##local"           => not_namespaces.push(None),
7441                    "##targetNamespace" => not_namespaces.push(target_ns.clone()),
7442                    "##any" | "##other" => return Err(SchemaCompileError::msg(format!(
7443                        "wildcard notNamespace token {tok:?} is only valid as the \
7444                         sole value, not part of a list (XSD 1.1 §3.10.2)"
7445                    ))),
7446                    other if other.starts_with("##") => return Err(SchemaCompileError::msg(format!(
7447                        "wildcard notNamespace token {other:?} is not a defined keyword"
7448                    ))),
7449                    other => not_namespaces.push(Some(Arc::from(other))),
7450                }
7451            }
7452        }
7453    }
7454    let ns_str = attrs.iter()
7455        .find(|a| a.name() == "namespace")
7456        .map(|a| a.value.as_ref())
7457        .unwrap_or("##any");
7458    // XSD §3.10.2: the `namespace` attribute is either `##any`,
7459    // `##other`, or a whitespace-separated list of (`##local` |
7460    // `##targetNamespace` | anyURI). `##any` and `##other` cannot
7461    // appear in the list form, only standalone.
7462    let namespaces = match ns_str {
7463        "##any"   => NamespaceConstraint::Any,
7464        "##other" => NamespaceConstraint::Other,
7465        list => {
7466            let mut out = Vec::new();
7467            for tok in list.split_whitespace() {
7468                match tok {
7469                    "##any" | "##other" => return Err(SchemaCompileError::msg(format!(
7470                        "wildcard namespace {tok:?} is only valid as the sole value, \
7471                         not part of a list (XSD §3.10.2)"
7472                    ))),
7473                    "##local"           => out.push(None),
7474                    "##targetNamespace" => out.push(target_ns.clone()),
7475                    other if other.starts_with("##") => return Err(SchemaCompileError::msg(format!(
7476                        "wildcard namespace {other:?} is not a defined keyword \
7477                         (expected '##any', '##other', '##local', or '##targetNamespace')"
7478                    ))),
7479                    other               => out.push(Some(Arc::from(other))),
7480                }
7481            }
7482            NamespaceConstraint::List(out)
7483        }
7484    };
7485    let process_contents = match attrs.iter()
7486        .find(|a| a.name() == "processContents")
7487        .map(|a| a.value.as_ref())
7488    {
7489        Some("lax")    => ProcessContents::Lax,
7490        Some("skip")   => ProcessContents::Skip,
7491        Some("strict") => ProcessContents::Strict,
7492        None           => ProcessContents::Strict,
7493        Some(other)    => return Err(SchemaCompileError::msg(format!(
7494            "wildcard processContents={other:?}: must be 'lax', 'skip', or 'strict'"
7495        ))),
7496    };
7497    Ok(Wildcard {
7498        namespaces, process_contents,
7499        not_qnames, not_namespaces,
7500        not_qname_defined, not_qname_defined_sibling,
7501    })
7502}
7503
7504/// Construct a `TypeRef` for the implicit `xs:anyType`: a complex
7505/// type whose content is an unbounded wildcard accepting any element,
7506/// whose attribute use is an `anyAttribute` wildcard, and whose
7507/// process-contents mode is `Lax` per XSD §3.4.7.  Used as the
7508/// type-def for `<xs:element>` declarations that omit `type=` and
7509/// don't carry an inline type — the spec's default.
7510fn any_type_ref() -> TypeRef {
7511    let wildcard = Wildcard {
7512        namespaces:                NamespaceConstraint::Any,
7513        process_contents:          ProcessContents::Lax,
7514        not_qnames:                Vec::new(),
7515        not_namespaces:            Vec::new(),
7516        not_qname_defined:         false,
7517        not_qname_defined_sibling: false,
7518    };
7519    let content = ContentModel::Complex {
7520        root: Particle {
7521            min_occurs: 0,
7522            max_occurs: MaxOccurs::Unbounded,
7523            term: Term::Wildcard(wildcard.clone()),
7524        },
7525        mixed: true,
7526    };
7527    TypeRef::Complex(Arc::new(ComplexType {
7528        name:          Some(QName::xsd("anyType")),
7529        derivation:    None,
7530        content,
7531        matcher:       std::sync::OnceLock::new(),
7532        attributes:    Vec::new(),
7533        any_attribute: Some(wildcard),
7534        abstract_:     false,
7535        block:         BlockSet::default(),
7536        final_:        BlockSet::default(),
7537        pending_attribute_group_refs: Vec::new(),
7538        assertions: Vec::new(),
7539    }))
7540}
7541
7542fn parse_bound(s: &str, builtin: BuiltinType) -> Result<Bound, SchemaCompileError> {
7543    use BuiltinType::*;
7544    Ok(match builtin {
7545        Decimal => Bound::Decimal(s.parse().map_err(|e| SchemaCompileError::msg(format!("decimal bound: {e}")))?),
7546        Float   => Bound::Float(s.parse().map_err(|e| SchemaCompileError::msg(format!("float bound: {e}")))?),
7547        Double  => Bound::Double(s.parse().map_err(|e| SchemaCompileError::msg(format!("double bound: {e}")))?),
7548        Integer | Long | Int | Short | Byte
7549        | NonPositiveInteger | NegativeInteger
7550        | NonNegativeInteger | UnsignedInt | UnsignedShort | UnsignedByte
7551        | PositiveInteger | UnsignedLong
7552            => Bound::Int(s.parse().map_err(|e| SchemaCompileError::msg(format!("int bound: {e}")))?),
7553        DateTime | Date | Time | GYearMonth | GYear
7554        | GMonthDay | GDay | GMonth | Duration => {
7555            // Parse the bound into its value-space representation now
7556            // so check_order can compare via the per-type ordering.
7557            let v = super::types::SimpleType::of_builtin(builtin).validate(s)
7558                .map_err(|e| SchemaCompileError::msg(format!(
7559                    "{builtin:?} bound {s:?}: {}", e.message
7560                )))?;
7561            Bound::Value(v)
7562        }
7563        // String-like / binary / boolean bounds aren't well-defined
7564        // for order facets — store as a string Value so a comparison
7565        // attempt at validate time fails cleanly rather than at
7566        // compile time.
7567        _ => Bound::Value(super::types::Value::String(s.to_owned())),
7568    })
7569}
7570
7571/// Compare two bounds within the same numeric/temporal family for
7572/// the cross-facet `min* <= max*` checks. Returns `None` for pairs
7573/// whose representations don't admit a meaningful comparison
7574/// (mixed Float/Double, Value-vs-numeric, …); the cross-facet
7575/// validator treats `None` as "cannot prove violation" and lets
7576/// the bound stand — the runtime facet check will reject any
7577/// truly broken instance.
7578fn compare_bounds(a: &Bound, b: &Bound) -> Option<std::cmp::Ordering> {
7579    use rust_decimal::Decimal;
7580    match (a, b) {
7581        (Bound::Int(x),     Bound::Int(y))     => Some(x.cmp(y)),
7582        (Bound::Decimal(x), Bound::Decimal(y)) => Some(x.cmp(y)),
7583        (Bound::Float(x),   Bound::Float(y))   => x.partial_cmp(y),
7584        (Bound::Double(x),  Bound::Double(y))  => x.partial_cmp(y),
7585        (Bound::Int(x),     Bound::Decimal(y)) => i128::try_from(*y).ok().map(|y| x.cmp(&y))
7586            .or_else(|| Decimal::from(*x).partial_cmp(y)),
7587        (Bound::Decimal(x), Bound::Int(y))     => Some(x.cmp(&Decimal::from(*y))),
7588        // Date/time/duration bounds are kept as Bound::Value(_) by
7589        // parse_bound and share the per-type ordering in
7590        // super::types::Value's PartialOrd implementation.
7591        (Bound::Value(x),   Bound::Value(y))   => super::facets::compare_values(x, y),
7592        _ => None,
7593    }
7594}
7595
7596// ── tests ────────────────────────────────────────────────────────────────────
7597
7598#[cfg(test)]
7599mod tests {
7600    use super::*;
7601
7602    fn xsd_str(extra_decls: &str) -> String {
7603        format!(
7604            r#"<?xml version="1.0"?>
7605<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
7606           targetNamespace="urn:test"
7607           xmlns="urn:test">
7608{extra_decls}
7609</xs:schema>"#
7610        )
7611    }
7612
7613    #[test]
7614    fn compile_empty_schema() {
7615        let s = Schema::compile_str(&xsd_str("")).unwrap();
7616        assert_eq!(s.target_namespace(), Some("urn:test"));
7617        assert_eq!(s.elements().count(), 0);
7618    }
7619
7620    #[test]
7621    fn compile_single_element_with_builtin_type() {
7622        let s = Schema::compile_str(&xsd_str(
7623            r#"<xs:element name="age" type="xs:int"/>"#
7624        )).unwrap();
7625        let qn = QName::new(Some("urn:test"), "age");
7626        assert!(s.element(&qn).is_some());
7627    }
7628
7629    #[test]
7630    fn rejects_no_namespace_top_level_element() {
7631        // An unprefixed `<element>` directly under `<xs:schema>` lands in
7632        // no namespace, so it is not a valid schema component — reject it
7633        // rather than silently ignoring it (foreign-*namespace* elements
7634        // are still tolerated/skipped).
7635        let bad = r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
7636            <element name="a" type="xs:string"/>
7637        </xs:schema>"#;
7638        let err = Schema::compile_str(bad)
7639            .expect_err("no-namespace top-level element must be rejected");
7640        assert!(err.to_string().to_lowercase().contains("namespace"),
7641            "error should name the namespace problem: {err}");
7642    }
7643
7644    #[test]
7645    fn compile_simple_type_with_facets() {
7646        let s = Schema::compile_str(&xsd_str(r#"
7647            <xs:simpleType name="ZipCode">
7648                <xs:restriction base="xs:string">
7649                    <xs:pattern value="\d{5}(-\d{4})?"/>
7650                    <xs:maxLength value="10"/>
7651                </xs:restriction>
7652            </xs:simpleType>
7653        "#)).unwrap();
7654        let qn = QName::new(Some("urn:test"), "ZipCode");
7655        let TypeRef::Simple(st) = s.type_def(&qn).unwrap() else { panic!() };
7656        assert_eq!(st.builtin, BuiltinType::String);
7657        assert!(st.facets.facets.iter().any(|f| matches!(f, Facet::Pattern { .. })));
7658    }
7659
7660    #[test]
7661    fn restriction_chain_collapses_user_base_to_builtin_and_inherits_facets() {
7662        // `Bounded` derives from xs:integer with `minInclusive=0`;
7663        // `Limited` then derives from `Bounded` with `maxInclusive=100`.
7664        // Before the chain walk, `Limited.builtin` would have been
7665        // `String` (wrong value space) and the inherited `minInclusive`
7666        // would have been silently dropped — letting negative values
7667        // validate.
7668        let s = Schema::compile_str(&xsd_str(r#"
7669            <xs:simpleType name="Bounded">
7670                <xs:restriction base="xs:integer">
7671                    <xs:minInclusive value="0"/>
7672                </xs:restriction>
7673            </xs:simpleType>
7674            <xs:simpleType name="Limited">
7675                <xs:restriction base="Bounded">
7676                    <xs:maxInclusive value="100"/>
7677                </xs:restriction>
7678            </xs:simpleType>
7679        "#)).unwrap();
7680        let qn = QName::new(Some("urn:test"), "Limited");
7681        let TypeRef::Simple(st) = s.type_def(&qn).unwrap() else { panic!() };
7682        assert_eq!(st.builtin, BuiltinType::Integer,
7683            "user-defined base must collapse to the ultimate built-in");
7684
7685        let has_min = st.facets.facets.iter().any(|f|
7686            matches!(f, Facet::MinInclusive(_)));
7687        let has_max = st.facets.facets.iter().any(|f|
7688            matches!(f, Facet::MaxInclusive(_)));
7689        assert!(has_min && has_max,
7690            "derived type must inherit base's minInclusive AND keep its own maxInclusive");
7691
7692        // End-to-end: the inherited minInclusive must actually reject
7693        // out-of-range values.  Without inheritance, `-5` would pass.
7694        assert!(st.validate("50").is_ok());
7695        assert!(st.validate("-5").is_err(),
7696            "minInclusive=0 from base must still reject negatives");
7697        assert!(st.validate("150").is_err(),
7698            "maxInclusive=100 from derived must still reject overflows");
7699    }
7700
7701    #[test]
7702    fn compile_complex_type_sequence() {
7703        let s = Schema::compile_str(&xsd_str(r#"
7704            <xs:complexType name="Person">
7705                <xs:sequence>
7706                    <xs:element name="name" type="xs:string"/>
7707                    <xs:element name="age"  type="xs:int"/>
7708                </xs:sequence>
7709            </xs:complexType>
7710        "#)).unwrap();
7711        let qn = QName::new(Some("urn:test"), "Person");
7712        let TypeRef::Complex(ct) = s.type_def(&qn).unwrap() else { panic!() };
7713        match &ct.content {
7714            ContentModel::Complex { root: Particle { term: Term::Group { kind, particles }, .. }, .. } => {
7715                assert_eq!(*kind, GroupKind::Sequence);
7716                assert_eq!(particles.len(), 2);
7717            }
7718            _ => panic!("expected sequence"),
7719        }
7720    }
7721
7722    #[test]
7723    fn compile_complex_type_with_attributes() {
7724        let s = Schema::compile_str(&xsd_str(r#"
7725            <xs:complexType name="Item">
7726                <xs:sequence>
7727                    <xs:element name="title" type="xs:string"/>
7728                </xs:sequence>
7729                <xs:attribute name="id"    type="xs:int" use="required"/>
7730                <xs:attribute name="kind"  type="xs:string"/>
7731            </xs:complexType>
7732        "#)).unwrap();
7733        let qn = QName::new(Some("urn:test"), "Item");
7734        let TypeRef::Complex(ct) = s.type_def(&qn).unwrap() else { panic!() };
7735        assert_eq!(ct.attributes.len(), 2);
7736        assert_eq!(ct.attributes[0].use_kind, AttributeUseKind::Required);
7737        assert_eq!(ct.attributes[1].use_kind, AttributeUseKind::Optional);
7738    }
7739
7740    #[test]
7741    fn compile_choice_and_all() {
7742        let s = Schema::compile_str(&xsd_str(r#"
7743            <xs:complexType name="Either">
7744                <xs:choice>
7745                    <xs:element name="left"  type="xs:int"/>
7746                    <xs:element name="right" type="xs:string"/>
7747                </xs:choice>
7748            </xs:complexType>
7749            <xs:complexType name="Both">
7750                <xs:all>
7751                    <xs:element name="a" type="xs:int"/>
7752                    <xs:element name="b" type="xs:int"/>
7753                </xs:all>
7754            </xs:complexType>
7755        "#)).unwrap();
7756        let qn1 = QName::new(Some("urn:test"), "Either");
7757        if let TypeRef::Complex(ct) = s.type_def(&qn1).unwrap() {
7758            if let ContentModel::Complex { root: Particle { term: Term::Group { kind, .. }, .. }, .. } = &ct.content {
7759                assert_eq!(*kind, GroupKind::Choice);
7760            } else { panic!() }
7761        }
7762        let qn2 = QName::new(Some("urn:test"), "Both");
7763        if let TypeRef::Complex(ct) = s.type_def(&qn2).unwrap() {
7764            if let ContentModel::Complex { root: Particle { term: Term::Group { kind, .. }, .. }, .. } = &ct.content {
7765                assert_eq!(*kind, GroupKind::All);
7766            } else { panic!() }
7767        }
7768    }
7769
7770    #[test]
7771    fn compile_max_occurs_unbounded() {
7772        let s = Schema::compile_str(&xsd_str(r#"
7773            <xs:complexType name="Items">
7774                <xs:sequence>
7775                    <xs:element name="item" type="xs:string" maxOccurs="unbounded"/>
7776                </xs:sequence>
7777            </xs:complexType>
7778        "#)).unwrap();
7779        let qn = QName::new(Some("urn:test"), "Items");
7780        if let TypeRef::Complex(ct) = s.type_def(&qn).unwrap() {
7781            if let ContentModel::Complex { root: Particle { term: Term::Group { particles, .. }, .. }, .. } = &ct.content {
7782                assert_eq!(particles[0].max_occurs, MaxOccurs::Unbounded);
7783            }
7784        }
7785    }
7786
7787    #[test]
7788    fn import_with_unresolvable_location_is_a_soft_skip() {
7789        // XSD §4.2.3 — schemaLocation is a hint. compile_str uses
7790        // NoResolver, which declines every load. The schema must
7791        // still compile cleanly; declarations from the unloaded
7792        // document just aren't available.
7793        let xml = xsd_str(r#"<xs:import namespace="urn:other" schemaLocation="other.xsd"/>"#);
7794        Schema::compile_str(&xml).expect("schema with unresolvable import should still compile");
7795    }
7796
7797    #[test]
7798    fn import_without_location_is_silently_skipped() {
7799        // No schemaLocation means the schema relies on the consumer to
7800        // have made the imported namespace available some other way.
7801        let xml = xsd_str(r#"<xs:import namespace="urn:other"/>"#);
7802        assert!(Schema::compile_str(&xml).is_ok());
7803    }
7804
7805    #[test]
7806    fn include_via_in_memory_resolver() {
7807        use crate::xsd::InMemoryResolver;
7808        let main = r#"<?xml version="1.0"?>
7809<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
7810           targetNamespace="urn:test" xmlns="urn:test">
7811  <xs:include schemaLocation="types.xsd"/>
7812  <xs:element name="root" type="MyType"/>
7813</xs:schema>"#;
7814        let included = r#"<?xml version="1.0"?>
7815<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
7816           targetNamespace="urn:test" xmlns="urn:test">
7817  <xs:simpleType name="MyType">
7818    <xs:restriction base="xs:string">
7819      <xs:maxLength value="10"/>
7820    </xs:restriction>
7821  </xs:simpleType>
7822</xs:schema>"#;
7823        let resolver = InMemoryResolver::new().with("types.xsd", included.as_bytes().to_vec());
7824        let s = Schema::compile_with(main, resolver).unwrap();
7825        assert!(s.type_def(&QName::new(Some("urn:test"), "MyType")).is_some());
7826        assert!(s.element(&QName::new(Some("urn:test"), "root")).is_some());
7827    }
7828
7829    #[test]
7830    fn include_cycle_is_silently_skipped() {
7831        use crate::xsd::InMemoryResolver;
7832        let a = r#"<?xml version="1.0"?>
7833<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:t" xmlns="urn:t">
7834  <xs:include schemaLocation="b.xsd"/>
7835  <xs:element name="root" type="xs:string"/>
7836</xs:schema>"#;
7837        let b = r#"<?xml version="1.0"?>
7838<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:t" xmlns="urn:t">
7839  <xs:include schemaLocation="a.xsd"/>
7840</xs:schema>"#;
7841        let resolver = InMemoryResolver::new()
7842            .with("a.xsd", a.as_bytes().to_vec())
7843            .with("b.xsd", b.as_bytes().to_vec());
7844        // Should not infinite-loop; cycle detection short-circuits.
7845        let s = Schema::compile_with(a, resolver).unwrap();
7846        assert!(s.element(&QName::new(Some("urn:t"), "root")).is_some());
7847    }
7848
7849    #[test]
7850    fn include_target_ns_mismatch_rejected() {
7851        use crate::xsd::InMemoryResolver;
7852        let main = r#"<?xml version="1.0"?>
7853<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:a">
7854  <xs:include schemaLocation="other.xsd"/>
7855</xs:schema>"#;
7856        let other = r#"<?xml version="1.0"?>
7857<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:b">
7858  <xs:element name="x" type="xs:string"/>
7859</xs:schema>"#;
7860        let resolver = InMemoryResolver::new().with("other.xsd", other.as_bytes().to_vec());
7861        let err = Schema::compile_with(main, resolver).unwrap_err();
7862        assert!(err.message.contains("targetNamespace"));
7863    }
7864
7865    #[test]
7866    fn rejects_non_schema_root() {
7867        let err = Schema::compile_str("<root/>").unwrap_err();
7868        assert!(err.message.contains("xs:schema"));
7869    }
7870
7871    #[test]
7872    fn enumeration_facet_collected() {
7873        let s = Schema::compile_str(&xsd_str(r#"
7874            <xs:simpleType name="Color">
7875                <xs:restriction base="xs:string">
7876                    <xs:enumeration value="red"/>
7877                    <xs:enumeration value="green"/>
7878                    <xs:enumeration value="blue"/>
7879                </xs:restriction>
7880            </xs:simpleType>
7881        "#)).unwrap();
7882        let qn = QName::new(Some("urn:test"), "Color");
7883        if let TypeRef::Simple(st) = s.type_def(&qn).unwrap() {
7884            let enum_facet = st.facets.facets.iter().find_map(|f| match f {
7885                Facet::Enumeration(opts) => Some(opts),
7886                _ => None,
7887            }).unwrap();
7888            assert_eq!(enum_facet, &vec!["red".to_string(), "green".into(), "blue".into()]);
7889        }
7890    }
7891
7892    #[test]
7893    fn nested_groups_compile() {
7894        let s = Schema::compile_str(&xsd_str(r#"
7895            <xs:complexType name="Form">
7896                <xs:sequence>
7897                    <xs:choice>
7898                        <xs:element name="a" type="xs:int"/>
7899                        <xs:element name="b" type="xs:int"/>
7900                    </xs:choice>
7901                    <xs:element name="c" type="xs:string"/>
7902                </xs:sequence>
7903            </xs:complexType>
7904        "#));
7905        assert!(s.is_ok());
7906    }
7907}