Skip to main content

xsd_schema/validation/
runtime.rs

1//! `ValidationRuntime` — mutable per-run validation state.
2//!
3//! Created by [`SchemaValidator::start_run()`]. Holds the validation stack,
4//! identity constraint tables, sink, and all other per-run mutable state.
5//! Method bodies are moved verbatim from the former monolithic `SchemaValidator`.
6
7use std::collections::{HashMap, HashSet};
8use std::marker::PhantomData;
9
10use crate::arenas::{ComplexTypeDefData, ResolvedAttributeUse};
11use crate::compiler::{compile_content_model_matcher, SubstitutionGroupMap};
12use crate::ids::{
13    AttributeGroupKey, AttributeKey, ComplexTypeKey, ElementKey, IdentityConstraintKey, NameId,
14    NotationKey, SimpleTypeKey, TypeKey,
15};
16use crate::namespace::context::NamespaceContextSnapshot;
17use crate::namespace::qname::{parse_qname_with_snapshot, QNameError};
18use crate::namespace::table::well_known;
19use crate::parser::frames::{AttributeUseKind, AttributeUseResult, IdentityKind, ProcessContents};
20use crate::parser::location::SourceLocation;
21use crate::schema::model::DerivationSet;
22use crate::schema::resolver::format_resolved_qname;
23use crate::schema::SchemaSet;
24use crate::types::value::XmlValue;
25use crate::types::XmlTypeCode;
26#[cfg(feature = "xsd11")]
27use bumpalo::Bump;
28
29use super::content::ContentValidatorState;
30use super::context::{ElementValidationState, ValidatorState};
31use super::errors::{self, ValidationError};
32use super::identity::{CompiledIdentityConstraint, ConstraintStruct, KeyTable};
33#[cfg(feature = "xsd11")]
34use super::info::{AssertionOutcome, InheritedAttribute};
35use super::info::{
36    ContentProcessing, ContentType, DefaultAttribute, ExpectedAttribute, ExpectedElement,
37    NoNamespaceSchemaLocationHint, SchemaInfo, SchemaLocationHint, SchemaValidity, TypeSource,
38    ValidationAttempted, ValidationFlags,
39};
40use crate::types::complex::ProcessContents as TypesProcessContents;
41
42#[cfg(feature = "xsd11")]
43use super::assertions::{
44    evaluate_complex_type_assertions, has_inherited_assertions, AssertionBufferFrame,
45};
46#[cfg(feature = "xsd11")]
47use super::validator::AssertionSource;
48use super::validator::{ValidationSink, ValidationWarning};
49
50#[cfg(feature = "xsd11")]
51use crate::document::builder::BufferDocumentBuilder;
52#[cfg(feature = "xsd11")]
53use crate::document::BufferDocumentOptions;
54
55// ---------------------------------------------------------------------------
56// GroupAttribute — flat representation of an attribute from an attribute group
57// ---------------------------------------------------------------------------
58
59/// An attribute use collected from a resolved attribute group.
60struct GroupAttribute {
61    name: NameId,
62    namespace: Option<NameId>,
63    use_kind: AttributeUseKind,
64    type_key: Option<TypeKey>,
65    attr_key: Option<AttributeKey>,
66    fixed_value: Option<String>,
67    default_value: Option<String>,
68    #[cfg(feature = "xsd11")]
69    inheritable: bool,
70}
71
72// ---------------------------------------------------------------------------
73// AttributeLookup — three-state result from find_attribute_in_type
74// ---------------------------------------------------------------------------
75
76/// Result of looking up an attribute in a complex type's attribute list.
77enum AttributeLookup {
78    /// Found a matching attribute declaration
79    /// (attr_key, type_key, fixed_value, inheritable)
80    Found(Option<AttributeKey>, Option<TypeKey>, Option<String>, bool),
81    /// The attribute is explicitly prohibited
82    Prohibited,
83    /// No matching attribute found
84    NotFound,
85}
86
87// ---------------------------------------------------------------------------
88// XsiTypeOutcome — three-state result from resolve_xsi_type
89// ---------------------------------------------------------------------------
90
91/// Result of resolving an `xsi:type` attribute value.
92enum XsiTypeOutcome {
93    /// Successfully resolved and derivation-valid — use this type.
94    Applied(TypeKey),
95    /// QName invalid or type not found (cvc-elt.4.1).
96    Unresolved,
97    /// Type found but does not validly derive from declared type (cvc-elt.4.2).
98    InvalidDerivation,
99}
100
101// ---------------------------------------------------------------------------
102// ValidationRuntime
103// ---------------------------------------------------------------------------
104
105/// Mutable per-run validation state.
106///
107/// Created by [`super::validator::SchemaValidator::start_run()`].
108/// Holds the validation stack, identity constraint tables, sink, and all
109/// other per-run mutable state. The struct is explicitly `!Send + !Sync`.
110pub struct ValidationRuntime<'a, S: ValidationSink> {
111    /// The compiled schema set to validate against (borrowed from SchemaValidator)
112    pub(crate) schema_set: &'a SchemaSet,
113    /// Pre-built substitution group map (borrowed from SchemaValidator)
114    pub(crate) subst_groups: &'a Option<SubstitutionGroupMap>,
115    /// Validation flags controlling behaviour (copied from SchemaValidator)
116    pub(crate) flags: ValidationFlags,
117    /// Sink for errors and warnings
118    pub sink: S,
119    /// Stack of per-element validation states
120    validation_stack: Vec<ElementValidationState>,
121    /// Current state machine state
122    current_state: ValidatorState,
123    /// Current source location (updated by caller)
124    current_location: Option<SourceLocation>,
125    /// XPath-like element path (e.g., "/root/child[1]")
126    element_path: String,
127    /// Pre-compiled identity constraints (lazy cache; None = compilation failed)
128    compiled_constraints: HashMap<IdentityConstraintKey, Option<CompiledIdentityConstraint>>,
129    /// Active constraint state instances
130    active_constraints: Vec<ConstraintStruct>,
131    /// Collected ID values mapped to the owner element serial.
132    /// XSD 1.1 §3.17.5.2: same ID on the same owner element is allowed.
133    id_values: HashMap<String, u64>,
134    /// Monotonically increasing element serial counter for ID binding.
135    next_element_serial: u64,
136    /// Pending IDREF values: (value, location, element_path)
137    pending_idrefs: Vec<(String, Option<SourceLocation>, String)>,
138    /// Declared unparsed entity names from the document's DTD.
139    /// When set, ENTITY/ENTITIES values are checked against this set (§3.16.4).
140    unparsed_entities: Option<HashSet<String>>,
141    /// Per-element scope stack of key/unique tables
142    ic_scope_tables: Vec<Option<HashMap<IdentityConstraintKey, KeyTable>>>,
143    /// Keyrefs whose refer target was not yet available when they deactivated.
144    /// Carried upward and retried after each scope propagation.
145    deferred_keyrefs: Vec<(KeyTable, Option<IdentityConstraintKey>)>,
146    /// Which assertion evaluation path is active (XSD 1.1 only)
147    #[cfg(feature = "xsd11")]
148    pub(crate) assertion_source: AssertionSource,
149    /// Active fragment document builder (XSD 1.1).
150    /// SAFETY: borrows from fragment_arena via lifetime extension. Must be
151    /// dropped (.take()) before fragment_arena is reset/dropped.
152    /// Declared before fragment_arena to ensure correct drop order.
153    #[cfg(feature = "xsd11")]
154    fragment_builder: Option<BufferDocumentBuilder<'a>>,
155    /// Heap-stable bump arena for assertion fragments (XSD 1.1 only).
156    #[cfg(feature = "xsd11")]
157    fragment_arena: Option<Box<Bump>>,
158    /// Stack of assertion buffer frames (XSD 1.1).
159    #[cfg(feature = "xsd11")]
160    assertion_buffer_stack: Vec<AssertionBufferFrame>,
161    /// Deferred assertion frames from nested asserted elements (XSD 1.1).
162    #[cfg(feature = "xsd11")]
163    pending_assertion_frames: Vec<AssertionBufferFrame>,
164    /// Deferred attribute PSVI results from CTA processing (XSD 1.1).
165    #[cfg(feature = "xsd11")]
166    deferred_attribute_results: Vec<SchemaInfo>,
167    /// Final identity constraint tables after root element close (PSVI exposure).
168    final_ic_tables: Option<HashMap<IdentityConstraintKey, KeyTable>>,
169    /// Accumulated `xsi:schemaLocation` hints with base URI context.
170    schema_location_hints: Vec<SchemaLocationHint>,
171    /// Accumulated `xsi:noNamespaceSchemaLocation` hints with base URI context.
172    no_namespace_schema_location_hints: Vec<NoNamespaceSchemaLocationHint>,
173    /// First-introducer serial per namespace seen during validation.
174    /// Key: `Option<NameId>` (element namespace; `None` = absent namespace).
175    /// Value: `element_serial` of the first element using that namespace.
176    /// Used to enforce XSD 1.0 §4.3.2 Rule 4 ("late-arriving components"):
177    /// an `xsi:schemaLocation` / `xsi:noNamespaceSchemaLocation` hint that
178    /// announces a namespace whose first element appeared earlier than the
179    /// current element is rejected (XSD 1.0 only — XSD 1.1 §G.1.15 relaxes).
180    first_namespace_use: HashMap<Option<NameId>, u64>,
181    /// Base URI of the instance document (set by caller for relative URI resolution).
182    instance_base_uri: String,
183    /// Cached `xs:ID` simple-type key for XSD 1.0's ct-props-correct.5
184    /// "at most one ID-type attribute per element" runtime check. `None`
185    /// under XSD 1.1 (the rule was removed) so the per-attribute hot path
186    /// short-circuits without any built-in hash lookup or chain walk.
187    cached_xsd10_id_key: Option<SimpleTypeKey>,
188    /// `!Send + !Sync` marker
189    _not_thread_safe: PhantomData<*const ()>,
190}
191
192impl<'a, S: ValidationSink> ValidationRuntime<'a, S> {
193    /// Create a new `ValidationRuntime` (called by `SchemaValidator::start_run()`).
194    pub(crate) fn new(
195        schema_set: &'a SchemaSet,
196        subst_groups: &'a Option<SubstitutionGroupMap>,
197        flags: ValidationFlags,
198        sink: S,
199        #[cfg(feature = "xsd11")] assertion_source: AssertionSource,
200    ) -> Self {
201        let cached_xsd10_id_key = if schema_set.is_xsd10() {
202            schema_set.builtin_types().get_by_type_code(XmlTypeCode::Id)
203        } else {
204            None
205        };
206        ValidationRuntime {
207            schema_set,
208            subst_groups,
209            flags,
210            sink,
211            validation_stack: Vec::new(),
212            current_state: ValidatorState::None,
213            current_location: None,
214            element_path: String::new(),
215            compiled_constraints: HashMap::new(),
216            active_constraints: Vec::new(),
217            id_values: HashMap::new(),
218            next_element_serial: 0,
219            pending_idrefs: Vec::new(),
220            unparsed_entities: None,
221            ic_scope_tables: Vec::new(),
222            deferred_keyrefs: Vec::new(),
223            final_ic_tables: None,
224            schema_location_hints: Vec::new(),
225            no_namespace_schema_location_hints: Vec::new(),
226            first_namespace_use: HashMap::new(),
227            instance_base_uri: String::new(),
228            #[cfg(feature = "xsd11")]
229            assertion_source,
230            #[cfg(feature = "xsd11")]
231            fragment_builder: None,
232            #[cfg(feature = "xsd11")]
233            fragment_arena: None,
234            #[cfg(feature = "xsd11")]
235            assertion_buffer_stack: Vec::new(),
236            #[cfg(feature = "xsd11")]
237            pending_assertion_frames: Vec::new(),
238            #[cfg(feature = "xsd11")]
239            deferred_attribute_results: Vec::new(),
240            cached_xsd10_id_key,
241            _not_thread_safe: PhantomData,
242        }
243    }
244
245    /// Set the current source location for error reporting
246    pub fn set_location(&mut self, location: SourceLocation) {
247        self.current_location = Some(location);
248    }
249
250    /// Clear the current source location
251    pub fn clear_location(&mut self) {
252        self.current_location = None;
253    }
254
255    /// Returns the final identity constraint tables after validation completes.
256    ///
257    /// Only available after `end_validation()` succeeds. Contains key/unique/keyref
258    /// tables accumulated during the root element's validation scope.
259    pub fn identity_constraint_tables(&self) -> Option<&HashMap<IdentityConstraintKey, KeyTable>> {
260        self.final_ic_tables.as_ref()
261    }
262
263    /// Set the declared unparsed entity names from the document's DTD.
264    ///
265    /// When set, ENTITY/ENTITIES values are validated against this set per
266    /// §3.16.4 String Valid clause 3: "Every ENTITY value in V is a declared
267    /// entity name."
268    pub fn set_unparsed_entities(&mut self, entities: HashSet<String>) {
269        self.unparsed_entities = Some(entities);
270    }
271
272    /// Set the base URI of the instance document being validated.
273    ///
274    /// This base URI is attached to every schema-location hint collected
275    /// during validation so that relative URIs can be resolved correctly
276    /// when schemas are loaded later.
277    pub fn set_instance_base_uri(&mut self, base_uri: impl Into<String>) {
278        self.instance_base_uri = base_uri.into();
279    }
280
281    /// Returns accumulated `xsi:schemaLocation` hints.
282    ///
283    /// Each hint contains a namespace/location pair plus the instance base
284    /// URI for resolving relative locations. Complete pairs from every
285    /// `xsi:schemaLocation` attribute are included, even from attributes
286    /// that failed even-token-count enforcement (the complete pairs are
287    /// still valid hints). Any trailing unpaired token is ignored.
288    pub fn schema_location_hints(&self) -> &[SchemaLocationHint] {
289        &self.schema_location_hints
290    }
291
292    /// Returns accumulated `xsi:noNamespaceSchemaLocation` hints.
293    pub fn no_namespace_schema_location_hints(&self) -> &[NoNamespaceSchemaLocationHint] {
294        &self.no_namespace_schema_location_hints
295    }
296
297    /// Returns a reference to the fragment arena, if it has been allocated.
298    #[cfg(feature = "xsd11")]
299    #[cfg(test)]
300    fn fragment_arena(&self) -> Option<&Bump> {
301        self.fragment_arena.as_deref()
302    }
303
304    /// Returns a mutable reference to the fragment arena, allocating it on
305    /// first use.
306    ///
307    /// # Panics (debug)
308    /// Panics if `fragment_builder` is active, which would mean the arena
309    /// is borrowed and mutation would invalidate the builder's reference.
310    #[cfg(feature = "xsd11")]
311    #[cfg(test)]
312    fn fragment_arena_mut(&mut self) -> &mut Bump {
313        debug_assert!(
314            self.fragment_builder.is_none(),
315            "fragment_arena_mut() called while fragment_builder is active — \
316             would invalidate the builder's borrow"
317        );
318        self.fragment_arena
319            .get_or_insert_with(|| Box::new(Bump::new()))
320    }
321
322    // -----------------------------------------------------------------------
323    // Assertion buffering helpers (XSD 1.1)
324    // -----------------------------------------------------------------------
325
326    /// Returns `true` if assertion buffering is currently active.
327    #[cfg(feature = "xsd11")]
328    #[inline]
329    fn is_buffering_assertions(&self) -> bool {
330        !self.assertion_buffer_stack.is_empty()
331    }
332
333    /// Creates the arena + builder when the outermost asserted element is
334    /// encountered. Returns `false` if builder creation fails.
335    #[cfg(feature = "xsd11")]
336    fn begin_assertion_buffering(&mut self) -> bool {
337        let arena_box = self
338            .fragment_arena
339            .get_or_insert_with(|| Box::new(Bump::new()));
340        // SAFETY: Box<Bump> is heap-allocated — stable address across struct moves.
341        // The builder will be dropped (via .take()) before the arena is reset or dropped.
342        // Field declaration order guarantees fragment_builder drops before fragment_arena.
343        let arena_ref: &'a Bump = unsafe { &*(&**arena_box as *const Bump) };
344        let names_ref: &'a crate::namespace::table::NameTable = &self.schema_set.name_table;
345        // Pass the SchemaSet through so navigator.typed_value() can resolve
346        // bindings against the schema arenas. Without it the navigator
347        // short-circuits to TypedValue::Untyped (navigator.rs:683-686),
348        // and assertion XPath sees every typed attribute/element as
349        // xs:untypedAtomic — value-comparison operators then fail with
350        // "op:eq is not defined for xs:untypedAtomic and ...".
351        match BufferDocumentBuilder::new(
352            arena_ref,
353            names_ref,
354            Some(self.schema_set),
355            BufferDocumentOptions::fragment(),
356        ) {
357            Ok(builder) => {
358                self.fragment_builder = Some(builder);
359                true
360            }
361            Err(e) => {
362                self.report_error(
363                    "cvc-assertion",
364                    format!("Failed to create assertion fragment builder: {}", e),
365                );
366                false
367            }
368        }
369    }
370
371    /// Abort assertion buffering due to a builder error. Drops the builder,
372    /// clears all assertion state, and resets the arena. Called when a
373    /// forwarding operation (attribute/end_element) fails, to prevent a
374    /// desynchronized fragment from producing wrong assertion results.
375    #[cfg(feature = "xsd11")]
376    fn abort_assertion_buffering(&mut self, error_msg: String) {
377        self.report_error("cvc-assertion", error_msg);
378        // Drop builder before resetting arena (maintains safety invariant)
379        self.fragment_builder.take();
380        self.assertion_buffer_stack.clear();
381        self.pending_assertion_frames.clear();
382        if let Some(arena) = self.fragment_arena.as_mut() {
383            arena.reset();
384        }
385    }
386
387    /// Unified message format for assertion-fragment-buffer aborts.
388    #[cfg(feature = "xsd11")]
389    fn abort_assertion_buffer_op(&mut self, context: &str, e: impl std::fmt::Display) {
390        self.abort_assertion_buffering(format!(
391            "Assertion fragment buffer error ({}): {}",
392            context, e
393        ));
394    }
395
396    /// Install a schema binding on a buffered fragment node, aborting
397    /// the buffer with a labelled error on failure. Returns `false` if
398    /// the buffer was aborted, so callers can short-circuit.
399    #[cfg(feature = "xsd11")]
400    fn install_fragment_binding(
401        &mut self,
402        node_ref: u32,
403        binding: crate::document::type_remap::NodeSchemaBinding,
404        context: &str,
405    ) -> bool {
406        let Some(builder) = self.fragment_builder.as_mut() else {
407            return true;
408        };
409        if let Err(e) = builder.set_node_binding(node_ref, binding) {
410            self.abort_assertion_buffer_op(context, e);
411            return false;
412        }
413        true
414    }
415
416    /// Called after every `push_element()`. Detects whether the element's
417    /// complex type has assertions. If so, starts or extends assertion
418    /// buffering. Also forwards `start_element` to the builder for all
419    /// children within an active buffered scope.
420    #[cfg(feature = "xsd11")]
421    fn detect_assertions_on_element(
422        &mut self,
423        type_key: Option<TypeKey>,
424        local_name: NameId,
425        namespace: Option<NameId>,
426    ) {
427        if self.assertion_source != AssertionSource::FragmentBuffer {
428            return;
429        }
430        let has_assertions = match type_key {
431            Some(TypeKey::Complex(ct_key))
432                if has_inherited_assertions(ct_key, &self.schema_set.arenas) =>
433            {
434                Some(ct_key)
435            }
436            _ => None,
437        };
438
439        let force_start = self
440            .validation_stack
441            .last()
442            .is_some_and(|ev| ev.has_type_alternatives);
443
444        if !self.is_buffering_assertions() && has_assertions.is_none() && !force_start {
445            return; // nothing to do
446        }
447
448        // Start buffering if this is the outermost asserted element
449        if !self.is_buffering_assertions() && !self.begin_assertion_buffering() {
450            return; // builder creation failed — error already reported
451        }
452
453        // Forward start_element to builder (all children in scope).
454        // Materialize ns_declarations from the live in-scope bindings so the
455        // fragment element exposes the same namespace nodes, enabling
456        // `fn:in-scope-prefixes()` and prefixed-QName assertions to see
457        // every prefix declared by the element or its ancestors.
458        let local = self.schema_set.name_table.resolve(local_name);
459        let ns = namespace
460            .map(|id| self.schema_set.name_table.resolve(id).to_string())
461            .unwrap_or_default();
462        let mut ns_strings: Vec<(String, String)> = Vec::new();
463        if let Some(snapshot) = self
464            .validation_stack
465            .last()
466            .and_then(|ev| ev.ns_context.as_ref())
467        {
468            for &(prefix_id, uri_id) in &snapshot.bindings {
469                let prefix = self.schema_set.name_table.resolve(prefix_id).to_string();
470                let uri = self.schema_set.name_table.resolve(uri_id).to_string();
471                ns_strings.push((prefix, uri));
472            }
473            if let Some(default_ns) = snapshot.default_ns {
474                let uri = self.schema_set.name_table.resolve(default_ns).to_string();
475                ns_strings.push((String::new(), uri));
476            }
477        }
478        let ns_decls: Vec<(&str, &str)> = ns_strings
479            .iter()
480            .map(|(p, u)| (p.as_str(), u.as_str()))
481            .collect();
482        let element_ref = match self.fragment_builder.as_mut() {
483            Some(builder) => match builder.start_element(&local, &ns, "", &ns_decls) {
484                Ok(r) => r,
485                Err(e) => {
486                    self.report_error(
487                        "cvc-assertion",
488                        format!("Assertion fragment buffer error (start_element): {}", e),
489                    );
490                    return;
491                }
492            },
493            None => return, // builder was not created (error already reported)
494        };
495
496        // Install schema binding for descendant elements so assertion
497        // XPath sees their declared type. The asserter element itself
498        // is left unbound — per §3.13.4.1 (note around clause 2.3.1.3),
499        // it has annotation `anyType`, so `data(.)` and `string(.)`
500        // yield xs:untypedAtomic; the typed value is exposed via
501        // `$value`. Observable in saxonData/Assert/assert014–017.
502        if has_assertions.is_none() {
503            if let Some(tk) = type_key {
504                let (element_decl, content_type) = self
505                    .validation_stack
506                    .last()
507                    .map(|ev| (ev.element_decl, ev.content_type))
508                    .unwrap_or((None, None));
509                let binding = crate::document::type_remap::NodeSchemaBinding {
510                    type_key: tk,
511                    element_decl,
512                    attribute_decl: None,
513                    content_type,
514                };
515                if !self.install_fragment_binding(element_ref, binding, "element binding") {
516                    return;
517                }
518            }
519        }
520
521        // Save element_ref for potential CTA re-detection
522        if let Some(ev) = self.validation_stack.last_mut() {
523            ev.assertion_element_ref = Some(element_ref);
524        }
525
526        // Push assertion frame if this element has assertions
527        if let Some(ct_key) = has_assertions {
528            self.assertion_buffer_stack.push(AssertionBufferFrame {
529                element_ref,
530                complex_type_key: ct_key,
531                element_path: String::new(), // populated at end-element
532                location: None,              // populated at end-element
533            });
534            if let Some(ev) = self.validation_stack.last_mut() {
535                ev.owns_assertion_buffer = true;
536            }
537        }
538    }
539
540    /// Re-detect assertions after CTA switched the type, without
541    /// re-emitting `start_element` on the fragment builder. Pops any
542    /// stale assertion frame for the old type and pushes a new one
543    /// for the new type if it carries inherited assertions.
544    #[cfg(feature = "xsd11")]
545    fn redetect_assertions_after_cta(&mut self, new_type: Option<TypeKey>) {
546        if self.assertion_source != AssertionSource::FragmentBuffer {
547            return;
548        }
549
550        // Pop old assertion frame, saving its element_ref
551        let old_element_ref = if let Some(ev) = self.validation_stack.last_mut() {
552            if ev.owns_assertion_buffer {
553                let frame = self.assertion_buffer_stack.pop();
554                ev.owns_assertion_buffer = false;
555                frame.map(|f| f.element_ref)
556            } else {
557                None
558            }
559        } else {
560            return;
561        };
562
563        // Check if new type has assertions
564        let new_ct_key = match new_type {
565            Some(TypeKey::Complex(ct_key))
566                if has_inherited_assertions(ct_key, &self.schema_set.arenas) =>
567            {
568                ct_key
569            }
570            _ => {
571                // New type has no assertions. If no parent is buffering,
572                // tear down the builder to avoid a dangling fragment_builder
573                // at end_validation. This covers both the case where we
574                // popped an old frame (old_element_ref is Some) and the
575                // force_start case where no frame was ever pushed
576                // (old_element_ref is None but fragment_builder exists).
577                if self.assertion_buffer_stack.is_empty() && self.fragment_builder.is_some() {
578                    self.fragment_builder.take();
579                    self.pending_assertion_frames.clear();
580                    if let Some(arena) = self.fragment_arena.as_mut() {
581                        arena.reset();
582                    }
583                }
584                return;
585            }
586        };
587
588        // Get element_ref: prefer the old frame's ref, fall back to saved ref
589        let element_ref = old_element_ref.or_else(|| {
590            self.validation_stack
591                .last()
592                .and_then(|ev| ev.assertion_element_ref)
593        });
594
595        let Some(element_ref) = element_ref else {
596            return;
597        };
598
599        // Compute BEFORE pushing: replay needed when no own frame existed AND
600        // no parent was buffering (attrs weren't forwarded during attr phase).
601        let need_replay = old_element_ref.is_none() && !self.is_buffering_assertions();
602
603        self.assertion_buffer_stack.push(AssertionBufferFrame {
604            element_ref,
605            complex_type_key: new_ct_key,
606            element_path: String::new(),
607            location: None,
608        });
609        if let Some(ev) = self.validation_stack.last_mut() {
610            ev.owns_assertion_buffer = true;
611        }
612
613        if need_replay {
614            let collected: Vec<_> = match self.validation_stack.last() {
615                Some(ev) => ev.collected_attributes.clone(),
616                None => return,
617            };
618            for (ns, name, value) in &collected {
619                let local = self.schema_set.name_table.resolve(*name);
620                let ns_str = ns
621                    .map(|id| self.schema_set.name_table.resolve(id).to_string())
622                    .unwrap_or_default();
623                let result = self
624                    .fragment_builder
625                    .as_mut()
626                    .map(|b| b.attribute(&local, &ns_str, "", value));
627                if let Some(Err(e)) = result {
628                    self.abort_assertion_buffer_op("attribute replay", e);
629                    return;
630                }
631            }
632        }
633    }
634
635    /// Report assertion errors for the outermost frame (current element).
636    #[cfg(feature = "xsd11")]
637    fn report_assertion_errors(
638        &mut self,
639        assertion_errors: Vec<ValidationError>,
640        ev_state: &mut ElementValidationState,
641    ) {
642        for err in assertion_errors {
643            self.report_validation_error_to(err, &mut ev_state.error_codes);
644            ev_state.validity = SchemaValidity::Invalid;
645        }
646    }
647
648    /// Report assertion errors for deferred (nested) frames using their stored
649    /// path and location instead of the current runtime state.
650    ///
651    /// Note: deferred errors are reported to the sink but do **not** affect the
652    /// outer (current) element's `SchemaValidity`. This is intentional per XSD
653    /// 1.1 §3.13.4.1: each element's validity is determined by its own type's
654    /// assertions, not by those of descendant elements.
655    #[cfg(feature = "xsd11")]
656    fn report_assertion_errors_deferred(
657        &mut self,
658        assertion_errors: Vec<ValidationError>,
659        element_path: &str,
660        location: &Option<SourceLocation>,
661    ) {
662        for err in assertion_errors {
663            let err = if !element_path.is_empty() {
664                err.with_path(element_path.to_string())
665            } else {
666                err
667            };
668            let err = match location {
669                Some(loc) => err.with_location(loc.clone()),
670                None => err,
671            };
672            self.sink.on_error(err);
673        }
674    }
675
676    // -----------------------------------------------------------------------
677    // Push API
678    // -----------------------------------------------------------------------
679
680    /// Validate an element start event (string-based lookup)
681    ///
682    /// `local_name` and `namespace_uri` identify the element.
683    /// `xsi_type` is the value of `xsi:type` (if present), as a raw QName string.
684    /// `xsi_nil` is the value of `xsi:nil` (if present).
685    /// `ns_context` is used to resolve the xsi:type QName prefix.
686    pub fn validate_element(
687        &mut self,
688        local_name: &str,
689        namespace_uri: &str,
690        xsi_type: Option<&str>,
691        xsi_nil: Option<&str>,
692        ns_context: &NamespaceContextSnapshot,
693    ) -> SchemaInfo {
694        let name_id = self.schema_set.name_table.add(local_name);
695        let ns_id = if namespace_uri.is_empty() {
696            None
697        } else {
698            Some(self.schema_set.name_table.add(namespace_uri))
699        };
700        self.validate_element_by_id(name_id, ns_id, xsi_type, xsi_nil, ns_context)
701    }
702
703    /// Push a skip-wildcard-matched element onto the stack with
704    /// `process_contents=Skip`, `validity=NotKnown`, and no content-model
705    /// validation. Returns the empty SchemaInfo callers propagate on skip.
706    fn push_skipped_element(
707        &mut self,
708        local_name: NameId,
709        namespace: Option<NameId>,
710        ns_context: &NamespaceContextSnapshot,
711    ) -> SchemaInfo {
712        let mut ev_state = ElementValidationState::new(local_name, namespace);
713        ev_state.ns_context = Some(ns_context.clone());
714        ev_state.process_contents = ContentProcessing::Skip;
715        ev_state.content_state = ContentValidatorState::Simple;
716        ev_state.validity = SchemaValidity::NotKnown;
717        self.push_element(ev_state);
718        self.advance_constraints_start_element_skipped(local_name, namespace);
719        #[cfg(feature = "xsd11")]
720        self.detect_assertions_on_element(None, local_name, namespace);
721        SchemaInfo::empty()
722    }
723
724    /// XSD 1.1 dynamic EDC (§3.4.6.4 / cvc-complex-type rule 5): when a
725    /// wildcard accepts an element and resolves it to a governing type/decl,
726    /// that governing binding must be consistent with any locally declared
727    /// element binding for the same QName in the same content model. Returns
728    /// `Some(reason)` on violation; the caller should mark the child invalid
729    /// and emit a `cvc-complex-type.5` error after push.
730    #[cfg(feature = "xsd11")]
731    fn dynamic_edc_violation_reason(
732        &self,
733        matched_via_wildcard: bool,
734        local_name: NameId,
735        namespace: Option<NameId>,
736        governing_type: Option<TypeKey>,
737        governing_decl: Option<ElementKey>,
738    ) -> Option<String> {
739        if !matched_via_wildcard || !self.schema_set.is_xsd11() {
740            return None;
741        }
742        let parent_ct = match self.validation_stack.last().and_then(|p| p.schema_type) {
743            Some(TypeKey::Complex(ct_key)) => ct_key,
744            _ => return None,
745        };
746        match crate::schema::edc::check_dynamic_edc(
747            self.schema_set,
748            self.subst_groups.as_ref(),
749            parent_ct,
750            (namespace, local_name),
751            governing_type,
752            governing_decl,
753        ) {
754            crate::schema::edc::EdcOutcome::Mismatch { reason } => Some(reason),
755            _ => None,
756        }
757    }
758
759    /// Validate an element start event (NameId fast-path)
760    pub fn validate_element_by_id(
761        &mut self,
762        local_name: NameId,
763        namespace: Option<NameId>,
764        xsi_type: Option<&str>,
765        xsi_nil: Option<&str>,
766        ns_context: &NamespaceContextSnapshot,
767    ) -> SchemaInfo {
768        // 1. State machine check
769        if !self.current_state.can_start_element() {
770            self.report_error(
771                "cvc-complex-type",
772                format!(
773                    "Element start not allowed in current state {:?}",
774                    self.current_state
775                ),
776            );
777            return SchemaInfo::invalid();
778        }
779
780        // 1b. Root element: verify PROCESS_ASSERTIONS ↔ AssertionSource consistency
781        #[cfg(feature = "xsd11")]
782        if self.validation_stack.is_empty() {
783            let has_flag = self.flags.contains(ValidationFlags::PROCESS_ASSERTIONS);
784            let is_fragment = self.assertion_source == AssertionSource::FragmentBuffer;
785            assert!(
786                has_flag == is_fragment,
787                "PROCESS_ASSERTIONS flag and AssertionSource are inconsistent: \
788                 flag={has_flag}, source={:?}. Call set_assertion_source() before validation.",
789                self.assertion_source,
790            );
791        }
792
793        // 2. If not root: advance parent's content model
794        let mut match_info: Option<super::content::ElementMatchInfo> = None;
795        let mut content_model_accepted = false;
796        let mut content_model_error = None;
797        let mut nil_error: Option<String> = None;
798        if let Some(parent) = self.validation_stack.last_mut() {
799            if parent.process_contents == ContentProcessing::Skip {
800                // Skipped element: don't validate content model, push as skip, return
801                parent.has_element_children = true;
802                return self.push_skipped_element(local_name, namespace, ns_context);
803            } else if parent.is_nil {
804                let parent_name = self
805                    .schema_set
806                    .name_table
807                    .resolve(parent.local_name)
808                    .to_string();
809                nil_error = Some(format!(
810                    "Element '{}' is nilled (xsi:nil='true') but has child element content",
811                    parent_name,
812                ));
813            } else {
814                parent.has_element_children = true;
815                // Derive wildcard target namespace from the parent's schema
816                // type, not the instance element namespace. For unqualified
817                // local elements parent.namespace may be None while the
818                // wildcard's ##targetNamespace should resolve to the schema
819                // document's target namespace (XSD spec §3.10.4).
820                let wildcard_target_ns = match parent.schema_type {
821                    Some(TypeKey::Complex(ct_key)) => {
822                        self.schema_set.arenas.complex_types[ct_key].target_namespace
823                    }
824                    _ => parent.namespace,
825                };
826                match parent.content_state.advance_element(
827                    local_name,
828                    namespace,
829                    wildcard_target_ns,
830                    self.schema_set.xsd_version,
831                    self.subst_groups.as_ref(),
832                ) {
833                    Some(info) => {
834                        match_info = Some(info);
835                        content_model_accepted = true;
836                    }
837                    None => {
838                        let elem_name = self.schema_set.name_table.resolve(local_name);
839                        content_model_error = Some(format!(
840                            "Element '{}' is not allowed at this position in the content model",
841                            elem_name,
842                        ));
843                    }
844                }
845            }
846        }
847        if let Some(msg) = nil_error {
848            self.report_error("cvc-elt.3.2.1", msg);
849        }
850        if let Some(msg) = content_model_error {
851            self.report_error("cvc-complex-type.2.4", msg);
852        }
853
854        // 3. Look up element declaration: prefer content model match, fall back to global
855        let matched_elem_key = match_info.and_then(|i| i.element_key);
856        let matched_type = match_info.and_then(|i| i.resolved_type);
857
858        // Was this element matched via a content-model wildcard (or open content)?
859        // Set when the matched particle was a wildcard, regardless of strict/lax/skip.
860        // §3.4.6.4 dynamic EDC fires only on wildcard matches.
861        #[cfg(feature = "xsd11")]
862        let matched_via_wildcard = match_info
863            .as_ref()
864            .and_then(|i| i.process_contents)
865            .is_some();
866
867        // Determine process_contents before element_key lookup: a skip wildcard
868        // must suppress the global declaration lookup (§3.10.4 cvc-wildcard).
869        let process_contents = match_info
870            .and_then(|i| i.process_contents)
871            .map(|pc| match pc {
872                TypesProcessContents::Strict => ContentProcessing::Strict,
873                TypesProcessContents::Lax => ContentProcessing::Lax,
874                TypesProcessContents::Skip => ContentProcessing::Skip,
875            })
876            .unwrap_or_else(|| {
877                self.validation_stack
878                    .last()
879                    .map(|p| p.process_contents)
880                    .unwrap_or(ContentProcessing::Strict)
881            });
882
883        // If the content model provided a resolved type for a local element,
884        // don't fall back to a global element with the same QName (it may have
885        // a different type).
886        let element_key = if matched_type.is_some() || process_contents == ContentProcessing::Skip {
887            matched_elem_key
888        } else {
889            matched_elem_key.or_else(|| self.schema_set.lookup_element(namespace, local_name))
890        };
891
892        if element_key.is_none() {
893            if content_model_accepted {
894                if process_contents == ContentProcessing::Skip {
895                    return self.push_skipped_element(local_name, namespace, ns_context);
896                }
897
898                // Content model accepted this element (wildcard in content model)
899                // but no global declaration exists.
900                let is_nil = matches!(xsi_nil, Some("true") | Some("1"));
901                let mut ev_state = ElementValidationState::new(local_name, namespace);
902                ev_state.ns_context = Some(ns_context.clone());
903                ev_state.validity = SchemaValidity::Valid;
904                ev_state.process_contents = process_contents;
905                ev_state.is_nil = is_nil;
906
907                let mut wildcard_xsi_type_errors = Vec::new();
908                if let Some(mut type_key) = matched_type {
909                    // PATH B1: xsi:type override for local elements with resolved type
910                    let mut b1_type_source = TypeSource::Declaration;
911                    if let Some(xsi_type_str) = xsi_type {
912                        match self.resolve_xsi_type(
913                            xsi_type_str,
914                            Some(type_key),
915                            DerivationSet::empty(),
916                            ns_context,
917                            &mut wildcard_xsi_type_errors,
918                        ) {
919                            XsiTypeOutcome::Applied(overridden) => {
920                                type_key = overridden;
921                                b1_type_source = TypeSource::XsiType;
922                            }
923                            XsiTypeOutcome::Unresolved | XsiTypeOutcome::InvalidDerivation => {
924                                ev_state.validity = SchemaValidity::Invalid;
925                                // keep original type_key
926                            }
927                        }
928                    }
929                    // Local element with resolved type — initialize content model
930                    let (content_state, content_type) = self.init_content_model(Some(type_key));
931                    ev_state.schema_type = Some(type_key);
932                    ev_state.type_source = Some(b1_type_source);
933                    ev_state.content_state = content_state;
934                    ev_state.content_type = Some(content_type);
935                } else {
936                    // PATH B2/B3: No declaration and no matched type.
937                    // Try xsi:type first — it can supply a governing type even
938                    // without a declaration.
939                    if let Some(xsi_type_str) = xsi_type {
940                        match self.resolve_xsi_type(
941                            xsi_type_str,
942                            None,
943                            DerivationSet::empty(),
944                            ns_context,
945                            &mut wildcard_xsi_type_errors,
946                        ) {
947                            XsiTypeOutcome::Applied(overridden) => {
948                                let (content_state, content_type) =
949                                    self.init_content_model(Some(overridden));
950                                ev_state.schema_type = Some(overridden);
951                                ev_state.type_source = Some(TypeSource::XsiType);
952                                ev_state.content_state = content_state;
953                                ev_state.content_type = Some(content_type);
954                            }
955                            XsiTypeOutcome::Unresolved | XsiTypeOutcome::InvalidDerivation => {
956                                // No governing type — lax assessment
957                                ev_state.validity = SchemaValidity::Invalid;
958                                let (content_state, content_type) =
959                                    self.lax_assessment_content_model();
960                                ev_state.content_state = content_state;
961                                ev_state.content_type = Some(content_type);
962                                // schema_type stays None (no governing type)
963                            }
964                        }
965                    } else {
966                        // PATH B3: No governing declaration/type — lax assessment via xs:anyType
967                        let (content_state, content_type) = self.lax_assessment_content_model();
968                        ev_state.content_state = content_state;
969                        ev_state.content_type = Some(content_type);
970                        // schema_type stays None
971                    }
972                    // Strict wildcard with no global declaration and no governing
973                    // type from xsi:type → cvc-elt.1.  Checked AFTER xsi:type so
974                    // that a valid xsi:type can still supply assessment.
975                    // Error deferred until after push so it lands on the child.
976                    if process_contents == ContentProcessing::Strict
977                        && ev_state.schema_type.is_none()
978                    {
979                        ev_state.validity = SchemaValidity::Invalid;
980                    }
981                }
982
983                ev_state.strictly_assessed = (ev_state.element_decl.is_some()
984                    || ev_state.schema_type.is_some())
985                    && ev_state.process_contents != ContentProcessing::Skip;
986                // §3.4.6.4 dynamic EDC for the no-element-key wildcard branch:
987                // even without a governing global declaration, an xsi:type may
988                // supply a governing type whose binding must agree with any
989                // QName-equal local element declaration in the parent CT.
990                #[cfg(feature = "xsd11")]
991                let edc_violation_b = self.dynamic_edc_violation_reason(
992                    matched_via_wildcard,
993                    local_name,
994                    namespace,
995                    ev_state.schema_type,
996                    None,
997                );
998                #[cfg(feature = "xsd11")]
999                if edc_violation_b.is_some() {
1000                    ev_state.validity = SchemaValidity::Invalid;
1001                }
1002                let schema_type = ev_state.schema_type;
1003                let content_type = ev_state.content_type;
1004                let validity = ev_state.validity;
1005                let type_source = ev_state.type_source;
1006                let needs_undeclared_error =
1007                    process_contents == ContentProcessing::Strict && schema_type.is_none();
1008                self.push_element(ev_state);
1009                #[cfg(feature = "xsd11")]
1010                if let Some(reason) = edc_violation_b {
1011                    let elem_name = self.schema_set.name_table.resolve(local_name).to_string();
1012                    self.report_error(
1013                        "cvc-complex-type.5",
1014                        format!(
1015                            "Element '{}' matched a wildcard but its governing type is \
1016                             inconsistent with the local element declaration in the \
1017                             parent's content model: {}",
1018                            elem_name, reason,
1019                        ),
1020                    );
1021                }
1022                // Emit deferred xsi:type errors now that the child is on the stack
1023                self.emit_deferred_xsi_type_errors(wildcard_xsi_type_errors);
1024                if needs_undeclared_error {
1025                    let elem_name = self.schema_set.name_table.resolve(local_name);
1026                    self.report_error(
1027                        "cvc-elt.1",
1028                        format!("Element '{}' is not declared", elem_name),
1029                    );
1030                }
1031                self.advance_constraints_start_element(local_name, namespace, None);
1032                #[cfg(feature = "xsd11")]
1033                self.detect_assertions_on_element(schema_type, local_name, namespace);
1034                return SchemaInfo {
1035                    element_decl: None,
1036                    attribute_decl: None,
1037                    schema_type,
1038                    member_type: None,
1039                    validity,
1040                    validation_attempted: ValidationAttempted::None,
1041                    is_default: false,
1042                    is_nil,
1043                    content_type,
1044                    typed_value: None,
1045                    normalized_value: None,
1046                    schema_error_codes: Vec::new(),
1047                    notation: None,
1048                    deferred_by_cta: false,
1049                    type_source,
1050                    #[cfg(feature = "xsd11")]
1051                    cta_selected: false,
1052                    #[cfg(feature = "xsd11")]
1053                    assertion_outcome: None,
1054                };
1055            }
1056
1057            // Root xsi:type promotion: at the document root with no governing
1058            // element declaration, an `xsi:type` attribute may itself supply
1059            // the governing type. XSD 1.0 §5.2 / 1.1 §5.2.2 — the element is
1060            // laxly assessed against the xsi:type-supplied type. This mirrors
1061            // PATH B2/B3 above (which handles the wildcard-child case) and
1062            // must run *before* the unconditional cvc-elt.1 fall-through.
1063            if self.validation_stack.is_empty() {
1064                if let Some(xsi_type_str) = xsi_type {
1065                    let mut deferred = Vec::new();
1066                    match self.resolve_xsi_type(
1067                        xsi_type_str,
1068                        None,
1069                        DerivationSet::empty(),
1070                        ns_context,
1071                        &mut deferred,
1072                    ) {
1073                        XsiTypeOutcome::Applied(type_key) => {
1074                            let is_nil = matches!(xsi_nil, Some("true") | Some("1"));
1075                            let mut ev_state =
1076                                ElementValidationState::new(local_name, namespace);
1077                            ev_state.ns_context = Some(ns_context.clone());
1078                            let (content_state, content_type) =
1079                                self.init_content_model(Some(type_key));
1080                            ev_state.schema_type = Some(type_key);
1081                            ev_state.type_source = Some(TypeSource::XsiType);
1082                            ev_state.content_state = content_state;
1083                            ev_state.content_type = Some(content_type);
1084                            ev_state.is_nil = is_nil;
1085                            ev_state.validity = SchemaValidity::Valid;
1086                            ev_state.process_contents = ContentProcessing::Strict;
1087                            ev_state.strictly_assessed = true;
1088                            self.push_element(ev_state);
1089                            self.advance_constraints_start_element(
1090                                local_name, namespace, None,
1091                            );
1092                            #[cfg(feature = "xsd11")]
1093                            self.detect_assertions_on_element(
1094                                Some(type_key),
1095                                local_name,
1096                                namespace,
1097                            );
1098                            return SchemaInfo {
1099                                element_decl: None,
1100                                attribute_decl: None,
1101                                schema_type: Some(type_key),
1102                                member_type: None,
1103                                validity: SchemaValidity::Valid,
1104                                validation_attempted: ValidationAttempted::None,
1105                                is_default: false,
1106                                is_nil,
1107                                content_type: Some(content_type),
1108                                typed_value: None,
1109                                normalized_value: None,
1110                                schema_error_codes: Vec::new(),
1111                                notation: None,
1112                                deferred_by_cta: false,
1113                                type_source: Some(TypeSource::XsiType),
1114                                #[cfg(feature = "xsd11")]
1115                                cta_selected: false,
1116                                #[cfg(feature = "xsd11")]
1117                                assertion_outcome: None,
1118                            };
1119                        }
1120                        XsiTypeOutcome::Unresolved | XsiTypeOutcome::InvalidDerivation => {
1121                            // xsi:type failed to resolve at the root — fall
1122                            // through to the Strict arm below for cvc-elt.1.
1123                            // Emit the deferred xsi:type diagnostic in
1124                            // addition, after push so attribution lands on
1125                            // the child.
1126                            let mut ev_state =
1127                                ElementValidationState::new(local_name, namespace);
1128                            ev_state.ns_context = Some(ns_context.clone());
1129                            ev_state.validity = SchemaValidity::Invalid;
1130                            let (content_state, content_type) =
1131                                self.lax_assessment_content_model();
1132                            ev_state.content_state = content_state;
1133                            ev_state.content_type = Some(content_type);
1134                            self.push_element(ev_state);
1135                            let elem_name =
1136                                self.schema_set.name_table.resolve(local_name).to_string();
1137                            self.report_error(
1138                                "cvc-elt.1",
1139                                format!("Element '{}' is not declared", elem_name),
1140                            );
1141                            self.emit_deferred_xsi_type_errors(deferred);
1142                            self.advance_constraints_start_element(
1143                                local_name, namespace, None,
1144                            );
1145                            #[cfg(feature = "xsd11")]
1146                            self.detect_assertions_on_element(None, local_name, namespace);
1147                            return SchemaInfo::invalid();
1148                        }
1149                    }
1150                }
1151            }
1152
1153            match process_contents {
1154                ContentProcessing::Skip => {
1155                    // Skip validation entirely
1156                    let mut ev_state = ElementValidationState::new(local_name, namespace);
1157                    ev_state.ns_context = Some(ns_context.clone());
1158                    ev_state.process_contents = ContentProcessing::Skip;
1159                    ev_state.content_state = ContentValidatorState::Simple; // accept anything
1160                    ev_state.validity = SchemaValidity::NotKnown;
1161                    self.push_element(ev_state);
1162                    self.advance_constraints_start_element(local_name, namespace, None);
1163                    #[cfg(feature = "xsd11")]
1164                    self.detect_assertions_on_element(None, local_name, namespace);
1165                    return SchemaInfo::empty();
1166                }
1167                ContentProcessing::Lax => {
1168                    // Lax: no declaration found — lax assessment via xs:anyType
1169                    let mut ev_state = ElementValidationState::new(local_name, namespace);
1170                    ev_state.ns_context = Some(ns_context.clone());
1171                    ev_state.process_contents = ContentProcessing::Lax;
1172                    let (content_state, content_type) = self.lax_assessment_content_model();
1173                    ev_state.content_state = content_state;
1174                    ev_state.content_type = Some(content_type);
1175                    // schema_type stays None — no governing type
1176                    ev_state.validity = SchemaValidity::NotKnown;
1177                    self.push_element(ev_state);
1178                    self.advance_constraints_start_element(local_name, namespace, None);
1179                    #[cfg(feature = "xsd11")]
1180                    self.detect_assertions_on_element(None, local_name, namespace);
1181                    return SchemaInfo::empty();
1182                }
1183                ContentProcessing::Strict => {
1184                    let mut ev_state = ElementValidationState::new(local_name, namespace);
1185                    ev_state.ns_context = Some(ns_context.clone());
1186                    ev_state.validity = SchemaValidity::Invalid;
1187                    // Lax assessment for content (same PSVI as lax when no declaration found)
1188                    let (content_state, content_type) = self.lax_assessment_content_model();
1189                    ev_state.content_state = content_state;
1190                    ev_state.content_type = Some(content_type);
1191                    // schema_type stays None
1192                    self.push_element(ev_state);
1193                    // Report error AFTER push so code lands on the child, not parent
1194                    let elem_name = self.schema_set.name_table.resolve(local_name);
1195                    self.report_error(
1196                        "cvc-elt.1",
1197                        format!("Element '{}' is not declared", elem_name),
1198                    );
1199                    self.advance_constraints_start_element(local_name, namespace, None);
1200                    #[cfg(feature = "xsd11")]
1201                    self.detect_assertions_on_element(None, local_name, namespace);
1202                    return SchemaInfo::invalid();
1203                }
1204            }
1205        }
1206
1207        let elem_key = element_key.unwrap();
1208        let elem_data = &self.schema_set.arenas.elements[elem_key];
1209
1210        // Check abstract (deferred until after push)
1211        let is_abstract = elem_data.is_abstract;
1212
1213        // §src-resolve: when an explicit `type` attribute could not be
1214        // resolved at compile time, the XSD 1.0 lazy path leaves a
1215        // `deferred_type_error` on the declaration. Surface it on first
1216        // selection rather than silently falling back to `xs:anyType`.
1217        let has_deferred_type_error = elem_data.deferred_type_error.is_some();
1218
1219        // 5. Resolve type from element declaration
1220        let mut type_key = elem_data.resolved_type;
1221
1222        // 6. xsi:type override
1223        // Errors are deferred and emitted after push so they land on the child element.
1224        let (effective_block, _) = crate::compiler::substitution::effective_element_constraints(
1225            self.schema_set,
1226            elem_data,
1227        );
1228        // Mask to element-relevant bits only (extension, restriction, substitution)
1229        // to avoid spuriously blocking list/union derivation steps.
1230        let effective_block = effective_block.element_block_mask();
1231        let mut xsi_type_deferred_errors = Vec::new();
1232        let mut xsi_type_invalid = false;
1233        let mut type_source = TypeSource::Declaration;
1234        if let Some(xsi_type_str) = xsi_type {
1235            // Default to anyType when no explicit type is declared (cvc-elt.4.3 still applies)
1236            let declared_for_xsi =
1237                type_key.or(Some(TypeKey::Complex(self.schema_set.any_type_key())));
1238            match self.resolve_xsi_type(
1239                xsi_type_str,
1240                declared_for_xsi,
1241                effective_block,
1242                ns_context,
1243                &mut xsi_type_deferred_errors,
1244            ) {
1245                XsiTypeOutcome::Applied(overridden) => {
1246                    type_key = Some(overridden);
1247                    type_source = TypeSource::XsiType;
1248                }
1249                XsiTypeOutcome::Unresolved | XsiTypeOutcome::InvalidDerivation => {
1250                    xsi_type_invalid = true;
1251                }
1252            }
1253        }
1254
1255        // §3.3.4.4 cvc-type clause 2: if T is a complex type definition, T.{abstract} must be false.
1256        // Hoist the complex-type fetch once so the cvc-type.2 error path below can
1257        // reuse the name/target_namespace without re-resolving the arena entry.
1258        let abstract_ct_info: Option<(Option<NameId>, Option<NameId>)> = if !xsi_type_invalid {
1259            if let Some(TypeKey::Complex(k)) = type_key {
1260                self.schema_set
1261                    .arenas
1262                    .complex_types
1263                    .get(k)
1264                    .filter(|ct| ct.is_abstract)
1265                    .map(|ct| (ct.name, ct.target_namespace))
1266            } else {
1267                None
1268            }
1269        } else {
1270            None
1271        };
1272        let abstract_type_invalid = abstract_ct_info.is_some();
1273
1274        // 7. xsi:nil
1275        let is_nil = if let Some(nil_str) = xsi_nil {
1276            nil_str == "true" || nil_str == "1"
1277        } else {
1278            false
1279        };
1280        // cvc-elt.3.1 (XSD 1.0 §3.3.4): the mere *presence* of xsi:nil on a
1281        // non-nillable element is invalid, regardless of value. xsi:nil="false"
1282        // on a non-nillable element still violates the rule because the element
1283        // declaration's {nillable} property must be true wherever xsi:nil
1284        // appears.
1285        let nillable_violation = xsi_nil.is_some() && !elem_data.nillable;
1286
1287        // 8. Initialize content model and determine ContentType
1288        let (content_state, content_type) = self.init_content_model(type_key);
1289
1290        // §3.4.6.4 dynamic EDC: when this element was matched by a wildcard
1291        // (not directly by an element particle), the resolved governing decl
1292        // and effective type must be consistent with any QName-equal local
1293        // element declaration in the parent CT.
1294        #[cfg(feature = "xsd11")]
1295        let edc_violation_a = self.dynamic_edc_violation_reason(
1296            matched_via_wildcard,
1297            local_name,
1298            namespace,
1299            type_key,
1300            Some(elem_key),
1301        );
1302
1303        // 9. Push ElementValidationState
1304        let mut ev_state = ElementValidationState::new(local_name, namespace);
1305        ev_state.ns_context = Some(ns_context.clone());
1306        ev_state.element_decl = Some(elem_key);
1307        ev_state.schema_type = type_key;
1308        ev_state.type_source = Some(type_source);
1309        ev_state.content_state = content_state;
1310        ev_state.content_type = Some(content_type);
1311        ev_state.is_nil = is_nil;
1312        ev_state.validity = if xsi_type_invalid
1313            || abstract_type_invalid
1314            || nillable_violation
1315            || has_deferred_type_error
1316        {
1317            SchemaValidity::Invalid
1318        } else {
1319            SchemaValidity::Valid
1320        };
1321        #[cfg(feature = "xsd11")]
1322        if edc_violation_a.is_some() {
1323            ev_state.validity = SchemaValidity::Invalid;
1324        }
1325        ev_state.process_contents = process_contents;
1326        // Strictly assessed: has governing declaration or type, and not skipped
1327        ev_state.strictly_assessed = (ev_state.element_decl.is_some()
1328            || ev_state.schema_type.is_some())
1329            && process_contents != ContentProcessing::Skip;
1330        #[cfg(feature = "xsd11")]
1331        {
1332            ev_state.has_type_alternatives = !self.schema_set.arenas.elements[elem_key]
1333                .alternatives
1334                .is_empty();
1335        }
1336        self.push_element(ev_state);
1337        #[cfg(feature = "xsd11")]
1338        if let Some(reason) = edc_violation_a.as_ref() {
1339            let elem_name = self.schema_set.name_table.resolve(local_name).to_string();
1340            self.report_error(
1341                "cvc-complex-type.5",
1342                format!(
1343                    "Element '{}' matched a wildcard but its governing element declaration \
1344                     is inconsistent with the local element declaration in the parent's \
1345                     content model: {}",
1346                    elem_name, reason,
1347                ),
1348            );
1349        }
1350
1351        // Emit deferred xsi:type errors now that the child is on the stack
1352        self.emit_deferred_xsi_type_errors(xsi_type_deferred_errors);
1353
1354        // Report deferred element-start errors AFTER push so codes land on the child
1355        if is_abstract {
1356            let elem_name = self.schema_set.name_table.resolve(local_name);
1357            self.report_error(
1358                "cvc-elt.2",
1359                format!(
1360                    "Element '{}' is abstract and cannot appear in instances",
1361                    elem_name
1362                ),
1363            );
1364        }
1365        if let Some((ct_name, ct_ns)) = abstract_ct_info {
1366            let type_name =
1367                crate::schema::derivation::format_type_name(self.schema_set, ct_name, ct_ns);
1368            self.report_error(
1369                "cvc-type.2",
1370                format!(
1371                    "Type '{}' is abstract and cannot be used to validate an element",
1372                    type_name
1373                ),
1374            );
1375        }
1376        if nillable_violation {
1377            let elem_name = self.schema_set.name_table.resolve(local_name);
1378            self.report_error(
1379                "cvc-elt.3.1",
1380                format!(
1381                    "Element '{}' is not nillable but xsi:nil was specified",
1382                    elem_name,
1383                ),
1384            );
1385        }
1386        if has_deferred_type_error {
1387            let msg = self.schema_set.arenas.elements[elem_key]
1388                .deferred_type_error
1389                .as_ref()
1390                .map(|d| d.message.as_str())
1391                .unwrap_or("");
1392            self.report_error("src-resolve", msg.to_string());
1393        }
1394
1395        self.advance_constraints_start_element(local_name, namespace, Some(elem_key));
1396
1397        // 9b. Assertion detection hook (XSD 1.1)
1398        #[cfg(feature = "xsd11")]
1399        self.detect_assertions_on_element(type_key, local_name, namespace);
1400
1401        // 10. Return SchemaInfo
1402        #[allow(unused_mut)]
1403        let mut validity = if xsi_type_invalid
1404            || abstract_type_invalid
1405            || has_deferred_type_error
1406        {
1407            SchemaValidity::Invalid
1408        } else {
1409            SchemaValidity::Valid
1410        };
1411        #[cfg(feature = "xsd11")]
1412        if edc_violation_a.is_some() {
1413            validity = SchemaValidity::Invalid;
1414        }
1415        SchemaInfo {
1416            element_decl: Some(elem_key),
1417            attribute_decl: None,
1418            schema_type: type_key,
1419            member_type: None,
1420            validity,
1421            validation_attempted: ValidationAttempted::None,
1422            is_default: false,
1423            is_nil,
1424            content_type: Some(content_type),
1425            typed_value: None,
1426            normalized_value: None,
1427            schema_error_codes: Vec::new(),
1428            notation: None,
1429            deferred_by_cta: false,
1430            type_source: Some(type_source),
1431            #[cfg(feature = "xsd11")]
1432            cta_selected: false,
1433            #[cfg(feature = "xsd11")]
1434            assertion_outcome: None,
1435        }
1436    }
1437
1438    /// Validate an attribute (string-based lookup)
1439    pub fn validate_attribute(
1440        &mut self,
1441        local_name: &str,
1442        namespace_uri: &str,
1443        value: &str,
1444    ) -> SchemaInfo {
1445        let name_id = self.schema_set.name_table.add(local_name);
1446        let ns_id = if namespace_uri.is_empty() {
1447            None
1448        } else {
1449            Some(self.schema_set.name_table.add(namespace_uri))
1450        };
1451        self.validate_attribute_by_id(name_id, ns_id, value)
1452    }
1453
1454    /// Validate an attribute (NameId fast-path)
1455    pub fn validate_attribute_by_id(
1456        &mut self,
1457        local_name: NameId,
1458        namespace: Option<NameId>,
1459        value: &str,
1460    ) -> SchemaInfo {
1461        // 1. State machine check
1462        if !self.current_state.can_validate_attribute() {
1463            self.report_error(
1464                "cvc-complex-type",
1465                format!(
1466                    "Attribute validation not allowed in current state {:?}",
1467                    self.current_state
1468                ),
1469            );
1470            return SchemaInfo::invalid();
1471        }
1472
1473        // Forward attribute to assertion fragment builder (XSD 1.1).
1474        // Done before ev_state borrow to avoid borrow conflict.
1475        // The returned attr_ref is stashed so we can install the schema
1476        // binding (type/decl/content_type) once validation has resolved
1477        // the attribute's type — without a binding, navigator.typed_value()
1478        // returns Untyped and assertion XPath value-comparisons reject the
1479        // attribute as xs:untypedAtomic (§3.13.4.1, §3.5).
1480        #[cfg(feature = "xsd11")]
1481        let fragment_attr_ref: Option<u32> = if self.is_buffering_assertions() {
1482            let local = self.schema_set.name_table.resolve(local_name);
1483            let ns = namespace
1484                .map(|id| self.schema_set.name_table.resolve(id).to_string())
1485                .unwrap_or_default();
1486            let attempt = self
1487                .fragment_builder
1488                .as_mut()
1489                .map(|b| b.attribute(&local, &ns, "", value));
1490            match attempt {
1491                Some(Ok(r)) => Some(r),
1492                Some(Err(e)) => {
1493                    self.abort_assertion_buffer_op("attribute", e);
1494                    None
1495                }
1496                None => None,
1497            }
1498        } else {
1499            None
1500        };
1501
1502        // Detect xml:base unconditionally (regardless of ALLOW_XML_ATTRIBUTES)
1503        // and update the current element's base URI for schema-location hints.
1504        // xml:base is compositional: relative values resolve against the
1505        // inherited base URI (RFC 3986 §5).
1506        // Only apply if this is the first xml:base on this element (skip
1507        // duplicates so an invalid repeated xml:base doesn't overwrite).
1508        let mut xml_base_rebase = None;
1509        if namespace == Some(well_known::XML_NAMESPACE)
1510            && local_name == self.schema_set.name_table.add("base")
1511        {
1512            if let Some(ev) = self.validation_stack.last_mut() {
1513                if !ev.base_uri_set_by_xml_base {
1514                    let xml_base = value.trim();
1515                    let base_uri = resolve_base_uri(xml_base, &ev.base_uri);
1516                    ev.base_uri = base_uri.clone();
1517                    ev.base_uri_set_by_xml_base = true;
1518                    xml_base_rebase = Some((
1519                        base_uri,
1520                        ev.schema_location_hint_start,
1521                        ev.no_namespace_schema_location_hint_start,
1522                    ));
1523                }
1524            }
1525        }
1526        if let Some((base_uri, sl_start, nnsl_start)) = xml_base_rebase {
1527            self.rebase_hint_range(sl_start, nnsl_start, &base_uri);
1528        }
1529
1530        let ev_state = match self.validation_stack.last_mut() {
1531            Some(s) => s,
1532            None => {
1533                self.report_error("cvc-complex-type", "No element context for attribute");
1534                return SchemaInfo::invalid();
1535            }
1536        };
1537
1538        // Validate xsi:* built-in attributes with proper type information
1539        if namespace == Some(well_known::XSI_NAMESPACE) {
1540            self.current_state = ValidatorState::Attribute;
1541            let ec_snapshot = self
1542                .validation_stack
1543                .last()
1544                .map(|ev| ev.error_codes.len())
1545                .unwrap_or(0);
1546            let mut result = self.validate_xsi_attribute(local_name, value);
1547            if let Some(ev) = self.validation_stack.last_mut() {
1548                // Record xsi:* attributes in seen_attributes so user-declared
1549                // attribute uses targeting xsi:type / xsi:nil / xsi:schemaLocation /
1550                // xsi:noNamespaceSchemaLocation (use="required") are satisfied
1551                // by the matching instance attribute. Without this insert,
1552                // `check_required_attributes` would always report a missing
1553                // required xsi:* attribute (saxon complex009/complex010).
1554                ev.seen_attributes.insert((namespace, local_name));
1555                // Extract attribute-specific error codes (mirrors normal attribute path)
1556                if ev.error_codes.len() > ec_snapshot {
1557                    result.schema_error_codes = ev.error_codes[ec_snapshot..].to_vec();
1558                    ev.error_codes.truncate(ec_snapshot);
1559                }
1560                // Track attribute [validation attempted] on parent element (§3.3.5.1)
1561                match result.validation_attempted {
1562                    ValidationAttempted::Full => {
1563                        ev.any_attr_not_none = true;
1564                    }
1565                    ValidationAttempted::None => {
1566                        ev.any_attr_not_full = true;
1567                    }
1568                    ValidationAttempted::Partial => {
1569                        ev.any_attr_not_full = true;
1570                        ev.any_attr_not_none = true;
1571                    }
1572                }
1573            }
1574            // XSD IC field XPaths (e.g. @*) match all attributes including xsi:*.
1575            // Feed xsi: attributes to IC field matching so that a field selecting
1576            // @* on an element with both schema and xsi: attributes correctly
1577            // detects the multi-node condition (cvc-identity-constraint.4.2.1).
1578            self.post_process_attribute(local_name, namespace, value, &result);
1579            #[cfg(feature = "xsd11")]
1580            self.bind_fragment_attribute(fragment_attr_ref, &result);
1581            return result;
1582        }
1583
1584        // Skip xml:* attributes when ALLOW_XML_ATTRIBUTES is set
1585        if namespace == Some(well_known::XML_NAMESPACE)
1586            && self.flags.contains(ValidationFlags::ALLOW_XML_ATTRIBUTES)
1587        {
1588            self.current_state = ValidatorState::Attribute;
1589            return SchemaInfo::empty();
1590        }
1591
1592        // 2. Duplicate check
1593        let attr_pair = (namespace, local_name);
1594        if !ev_state.seen_attributes.insert(attr_pair) {
1595            let attr_name = self.schema_set.name_table.resolve(local_name);
1596            self.report_error(
1597                "cvc-complex-type.3",
1598                format!("Duplicate attribute '{}'", attr_name),
1599            );
1600            if let Some(s) = self.validation_stack.last_mut() {
1601                s.validity = SchemaValidity::Invalid;
1602            }
1603            self.current_state = ValidatorState::Attribute;
1604            let mut result = SchemaInfo::invalid();
1605            result.schema_error_codes = vec!["cvc-complex-type.3"];
1606            return result;
1607        }
1608
1609        // When type alternatives are active, defer type-dependent attribute
1610        // validation until after CTA selection in validate_end_of_attributes().
1611        // Only collect the attribute data and perform type-independent checks.
1612        #[cfg(feature = "xsd11")]
1613        if ev_state.has_type_alternatives {
1614            ev_state
1615                .collected_attributes
1616                .push((namespace, local_name, value.to_string()));
1617            self.current_state = ValidatorState::Attribute;
1618            // Post-process without type info (IC field matching still works;
1619            // ID/IDREF will be handled during deferred validation).
1620            return SchemaInfo {
1621                deferred_by_cta: true,
1622                ..SchemaInfo::empty()
1623            };
1624        }
1625
1626        // Determine effective type for attribute validation.
1627        // When schema_type is None (no governing type) and the element is under
1628        // lax assessment, use xs:anyType which has anyAttribute processContents=lax.
1629        let type_key = ev_state.schema_type;
1630        let process_contents = ev_state.process_contents;
1631
1632        // When the element is skip-processed (matched by a processContents="skip"
1633        // xs:any wildcard), accept all attributes without schema validation.
1634        // The element is not assessed, so its attributes cannot generate cvc-complex-type
1635        // errors regardless of whether the element has a global declaration.
1636        if process_contents == ContentProcessing::Skip {
1637            self.current_state = ValidatorState::Attribute;
1638            let result = SchemaInfo::empty();
1639            self.post_process_attribute(local_name, namespace, value, &result);
1640            return result;
1641        }
1642
1643        let ct_key = match type_key {
1644            Some(TypeKey::Complex(ct)) => ct,
1645            None if process_contents != ContentProcessing::Skip => {
1646                // Lax assessment: validate attributes against xs:anyType
1647                // (anyAttribute processContents=lax accepts any attribute)
1648                self.schema_set.any_type_key()
1649            }
1650            _ => {
1651                // Simple-typed element: only xsi:* and (when permitted)
1652                // xml:* attributes are allowed. Any other attribute is a
1653                // cvc-complex-type.3.2.1 violation (no matching {attribute
1654                // use} on a simple type). xsi:* is already handled above;
1655                // xml:* under ALLOW_XML_ATTRIBUTES is also handled above.
1656                // Anything reaching here is an undeclared attribute on a
1657                // simple-typed element.
1658                self.current_state = ValidatorState::Attribute;
1659                let attr_name = self.schema_set.name_table.resolve(local_name);
1660                self.report_error(
1661                    "cvc-complex-type.3.2.1",
1662                    format!(
1663                        "Attribute '{}' is not allowed on a simple-typed element",
1664                        attr_name
1665                    ),
1666                );
1667                let mut result = SchemaInfo::invalid();
1668                result.schema_error_codes = vec!["cvc-complex-type.3.2.1"];
1669                self.post_process_attribute(local_name, namespace, value, &result);
1670                return result;
1671            }
1672        };
1673
1674        self.current_state = ValidatorState::Attribute;
1675        // Snapshot error_codes so we can extract attribute-specific codes
1676        let ec_snapshot = self
1677            .validation_stack
1678            .last()
1679            .map_or(0, |ev| ev.error_codes.len());
1680        let mut result = self.validate_attribute_against_type(ct_key, local_name, namespace, value);
1681        // ct-props-correct.5 (XSD 1.0 only) / cvc-complex-type per-element
1682        // ID attribute counting: an element instance may carry at most one
1683        // attribute whose effective type is xs:ID (or derived). XSD 1.1
1684        // explicitly relaxed this rule (the constraint was removed in the
1685        // 1.1 errata pass), so the check is gated on `cached_xsd10_id_key`
1686        // — populated only under XSD 1.0 (None otherwise), avoiding both
1687        // the per-attribute version check and the per-attribute built-in
1688        // hash lookup. (attZ014a/b)
1689        let attr_is_id = self.cached_xsd10_id_key.is_some_and(|id_key| {
1690            matches!(result.schema_type, Some(TypeKey::Simple(sk))
1691                if self.schema_set.derives_from(sk, id_key))
1692        });
1693        // Slice attribute-specific error codes and remove from parent
1694        if let Some(ev) = self.validation_stack.last_mut() {
1695            if ev.error_codes.len() > ec_snapshot {
1696                result.schema_error_codes = ev.error_codes[ec_snapshot..].to_vec();
1697                ev.error_codes.truncate(ec_snapshot);
1698            }
1699            // Track attribute [validation attempted] on parent element
1700            match result.validation_attempted {
1701                ValidationAttempted::Full => {
1702                    ev.any_attr_not_none = true;
1703                }
1704                ValidationAttempted::None => {
1705                    ev.any_attr_not_full = true;
1706                }
1707                ValidationAttempted::Partial => {
1708                    ev.any_attr_not_full = true;
1709                    ev.any_attr_not_none = true;
1710                }
1711            }
1712        }
1713        if attr_is_id {
1714            let already_seen = self
1715                .validation_stack
1716                .last()
1717                .is_some_and(|ev| ev.seen_id_attr);
1718            if already_seen {
1719                let attr_name = self.schema_set.name_table.resolve(local_name).to_string();
1720                self.report_error(
1721                    "cvc-complex-type",
1722                    format!(
1723                        "Element has more than one attribute of type xs:ID (or a type \
1724                         derived from xs:ID); '{}' is the second such attribute",
1725                        attr_name,
1726                    ),
1727                );
1728                result.validity = SchemaValidity::Invalid;
1729            } else if let Some(ev) = self.validation_stack.last_mut() {
1730                ev.seen_id_attr = true;
1731            }
1732        }
1733        #[cfg(feature = "xsd11")]
1734        self.bind_fragment_attribute(fragment_attr_ref, &result);
1735        result
1736    }
1737
1738    /// Install a schema binding (type/decl) on the buffered fragment
1739    /// attribute node so navigator.typed_value() returns the declared
1740    /// atomic type during XSD 1.1 assertion XPath evaluation. No-op
1741    /// when no fragment buffering is active or no type was resolved.
1742    #[cfg(feature = "xsd11")]
1743    fn bind_fragment_attribute(&mut self, attr_ref: Option<u32>, info: &SchemaInfo) {
1744        let Some(attr_ref) = attr_ref else { return };
1745        let Some(type_key) = info.schema_type else {
1746            return;
1747        };
1748        let binding = crate::document::type_remap::NodeSchemaBinding {
1749            type_key,
1750            element_decl: None,
1751            attribute_decl: info.attribute_decl,
1752            content_type: None,
1753        };
1754        let _ = self.install_fragment_binding(attr_ref, binding, "attr binding");
1755    }
1756
1757    /// Signal end of attributes; checks for missing required attributes
1758    pub fn validate_end_of_attributes(&mut self) -> SchemaInfo {
1759        if !self.current_state.can_end_attributes() {
1760            self.report_error(
1761                "cvc-complex-type",
1762                format!(
1763                    "End-of-attributes not allowed in current state {:?}",
1764                    self.current_state
1765                ),
1766            );
1767            return SchemaInfo::invalid();
1768        }
1769
1770        let schema_type = match self.validation_stack.last() {
1771            Some(s) => s.schema_type,
1772            None => {
1773                self.current_state = ValidatorState::EndOfAttributes;
1774                return SchemaInfo::empty();
1775            }
1776        };
1777
1778        // Evaluate type alternatives (XSD 1.1)
1779        #[cfg(feature = "xsd11")]
1780        let has_type_alternatives = self
1781            .validation_stack
1782            .last()
1783            .is_some_and(|s| s.has_type_alternatives);
1784
1785        #[cfg(feature = "xsd11")]
1786        let (schema_type, cta_switched, cta_selected) = if has_type_alternatives {
1787            let mut st = schema_type;
1788            let mut switched = false;
1789            let mut selected = false;
1790            if let Some(ev_state) = self.validation_stack.last() {
1791                if let Some(elem_key) = ev_state.element_decl {
1792                    // §3.12.4 clause 1.1.3: include [inherited attributes]
1793                    // that do not have the same expanded name as any of
1794                    // E's [attributes] when building the CTA XDM instance.
1795                    // Use incoming_inherited (the PSVI view) with the
1796                    // CTA-specific same-name exclusion.
1797                    let mut cta_attrs = ev_state.collected_attributes.clone();
1798                    let explicit_names: HashSet<(Option<NameId>, NameId)> =
1799                        cta_attrs.iter().map(|(ns, name, _)| (*ns, *name)).collect();
1800                    for ((ns, name), val) in &ev_state.incoming_inherited {
1801                        if !explicit_names.contains(&(*ns, *name)) {
1802                            cta_attrs.push((*ns, *name, val.value.clone()));
1803                        }
1804                    }
1805                    let new_type = super::alternatives::evaluate_type_alternatives(
1806                        elem_key,
1807                        ev_state.local_name,
1808                        ev_state.namespace,
1809                        &cta_attrs,
1810                        ev_state.ns_context.as_ref(),
1811                        Some(self.instance_base_uri.as_str()).filter(|s| !s.is_empty()),
1812                        self.schema_set,
1813                    );
1814                    if let Some(new_type_key) = new_type {
1815                        selected = true;
1816                        if Some(new_type_key) != st {
1817                            let (content_state, content_type) =
1818                                self.init_content_model(Some(new_type_key));
1819                            st = Some(new_type_key);
1820                            switched = true;
1821                            if let Some(ev) = self.validation_stack.last_mut() {
1822                                ev.schema_type = Some(new_type_key);
1823                                ev.content_state = content_state;
1824                                ev.content_type = Some(content_type);
1825                            }
1826                        }
1827                    }
1828                }
1829            }
1830            // Track CTA selection on ev_state regardless of type change.
1831            // Preserve XsiType as the governing source when xsi:type was
1832            // applied — per spec xsi:type takes precedence over CTA.
1833            if selected {
1834                if let Some(ev) = self.validation_stack.last_mut() {
1835                    ev.cta_selected = true;
1836                    if ev.type_source != Some(TypeSource::XsiType) {
1837                        ev.type_source = Some(TypeSource::TypeAlternative);
1838                    }
1839                }
1840            }
1841            (st, switched, selected)
1842        } else {
1843            (schema_type, false, false)
1844        };
1845
1846        #[cfg(not(feature = "xsd11"))]
1847        let cta_switched = false;
1848
1849        // When attributes were deferred for CTA, always validate them
1850        // against the (possibly unchanged) type.
1851        #[cfg(feature = "xsd11")]
1852        if has_type_alternatives {
1853            // Re-detect assertions BEFORE draining collected_attributes,
1854            // so redetect can replay them into the fragment builder.
1855            if cta_switched {
1856                self.redetect_assertions_after_cta(schema_type);
1857            }
1858            self.validate_deferred_attributes(schema_type);
1859        }
1860
1861        // Record inheritable attributes with default values for propagation (XSD 1.1)
1862        #[cfg(feature = "xsd11")]
1863        if let Some(TypeKey::Complex(ct_key)) = schema_type {
1864            self.record_inheritable_defaults(ct_key);
1865        }
1866
1867        // Check required attributes (clone seen_attributes to avoid borrow conflict)
1868        if let Some(TypeKey::Complex(ct_key)) = schema_type {
1869            let seen_attributes = match self.validation_stack.last() {
1870                Some(s) => s.seen_attributes.clone(),
1871                None => HashSet::new(),
1872            };
1873            let ct_data = &self.schema_set.arenas.complex_types[ct_key];
1874            if self.check_required_attributes(ct_data, &seen_attributes) {
1875                self.mark_current_invalid();
1876            }
1877        }
1878
1879        // Process default/fixed attribute values not explicitly provided in the
1880        // instance. A single pass handles both IC field matching (§3.11.4) and
1881        // ID/IDREF collection (§3.3.4 cvc-id.2) to avoid iterating twice.
1882        if let Some(TypeKey::Complex(ct_key)) = schema_type {
1883            let ct_data = &self.schema_set.arenas.complex_types[ct_key];
1884            let empty_seen = HashSet::new();
1885            let seen = self
1886                .validation_stack
1887                .last()
1888                .map(|s| &s.seen_attributes)
1889                .unwrap_or(&empty_seen);
1890            let has_ic = !self.active_constraints.is_empty();
1891            let builtin = self.schema_set.builtin_types();
1892            let id_key = builtin.get_by_type_code(XmlTypeCode::Id);
1893            let idref_key = builtin.get_by_type_code(XmlTypeCode::IdRef);
1894            let idrefs_key = builtin.get_by_type_code(XmlTypeCode::IdRefs);
1895            let entity_key = builtin.get_by_type_code(XmlTypeCode::Entity);
1896            let entities_key = builtin.get_by_type_code(XmlTypeCode::Entities);
1897            let mut ic_defaults: Vec<(NameId, NameId, String)> = Vec::new();
1898            let mut id_defaults: Vec<(String, TypeKey)> = Vec::new();
1899            for (i, attr_use) in ct_data.attributes.iter().enumerate() {
1900                if attr_use.use_kind == AttributeUseKind::Prohibited {
1901                    continue;
1902                }
1903                let resolved = ct_data.resolved_attributes.get(i);
1904                let (attr_name, attr_ns) =
1905                    self.resolve_attr_use_name_ns(attr_use, resolved, ct_data.target_namespace);
1906                if seen.contains(&(attr_ns, attr_name)) {
1907                    continue;
1908                }
1909                let attr_key = resolved.and_then(|r| r.resolved_ref);
1910                let ref_decl = attr_key.and_then(|k| self.schema_set.arenas.attributes.get(k));
1911                let value = attr_use
1912                    .attribute
1913                    .default_value
1914                    .as_deref()
1915                    .or(attr_use.attribute.fixed_value.as_deref())
1916                    .or_else(|| {
1917                        ref_decl
1918                            .and_then(|d| d.default_value.as_deref().or(d.fixed_value.as_deref()))
1919                    });
1920                let Some(v) = value else { continue };
1921                if has_ic {
1922                    ic_defaults.push((attr_name, attr_ns.unwrap_or(NameId(0)), v.to_string()));
1923                }
1924                // Check if this attribute's type is ID/IDREF/IDREFS/ENTITY/ENTITIES
1925                // (need to validate defaults for these types).
1926                let attr_type = resolved
1927                    .and_then(|r| r.resolved_type)
1928                    .or_else(|| ref_decl.and_then(|d| d.resolved_type));
1929                let needs_default_validation = match attr_type {
1930                    Some(TypeKey::Simple(sk)) => {
1931                        id_key == Some(sk)
1932                            || idref_key == Some(sk)
1933                            || idrefs_key == Some(sk)
1934                            || entity_key == Some(sk)
1935                            || entities_key == Some(sk)
1936                    }
1937                    _ => false,
1938                };
1939                if needs_default_validation {
1940                    if let Some(tk) = attr_type {
1941                        id_defaults.push((v.to_string(), tk));
1942                    }
1943                }
1944            }
1945            // Feed IC field matches (borrow-split: active_constraints borrows &mut self)
1946            let mut multi_node_defaults: Vec<(NameId, usize)> = Vec::new();
1947            for (name, ns, value) in ic_defaults {
1948                for cs in &mut self.active_constraints {
1949                    let matches = cs.matching_fields(name, ns);
1950                    for field_idx in matches {
1951                        let already_matched = cs.set_field_value(field_idx, value.clone(), None);
1952                        if already_matched {
1953                            multi_node_defaults.push((cs.key_table.constraint_name, field_idx));
1954                        }
1955                    }
1956                }
1957            }
1958            for (constraint_name, field_idx) in multi_node_defaults {
1959                let cname = self
1960                    .schema_set
1961                    .name_table
1962                    .resolve(constraint_name)
1963                    .to_string();
1964                self.report_error(
1965                    "cvc-identity-constraint.4.2.1",
1966                    format!(
1967                        "Identity constraint '{}': field {} matches more than one node",
1968                        cname,
1969                        field_idx + 1
1970                    ),
1971                );
1972            }
1973            // Validate and collect ID/IDREF values from absent defaults.
1974            // Owner is the current element (attributes bind to their element).
1975            let default_owner = self
1976                .validation_stack
1977                .last()
1978                .map(|e| e.element_serial)
1979                .unwrap_or(0);
1980            for (value, type_key) in id_defaults {
1981                if let Ok(result) =
1982                    super::simple::validate_simple_type(&value, type_key, self.schema_set)
1983                {
1984                    self.collect_id_idref(&result.typed_value, &value, default_owner);
1985                    self.check_entity_declared(&result.typed_value);
1986                }
1987            }
1988        }
1989
1990        // Forward to assertion fragment builder (XSD 1.1)
1991        #[cfg(feature = "xsd11")]
1992        if self.is_buffering_assertions() {
1993            if let Some(builder) = self.fragment_builder.as_mut() {
1994                builder.end_of_attributes();
1995            }
1996        }
1997
1998        self.current_state = ValidatorState::EndOfAttributes;
1999
2000        // When CTA switched the type or selected a type, return updated SchemaInfo
2001        // so callers (e.g. typed_builder) can update element bindings. Preserve prior
2002        // invalidity (e.g. from a bad xsi:type).
2003        if cta_switched {
2004            let ev = self.validation_stack.last();
2005            let content_type = ev.and_then(|s| s.content_type);
2006            let validity = ev.map(|s| s.validity).unwrap_or(SchemaValidity::NotKnown);
2007            return SchemaInfo {
2008                schema_type,
2009                content_type,
2010                validity,
2011                type_source: ev.and_then(|s| s.type_source),
2012                #[cfg(feature = "xsd11")]
2013                cta_selected: ev.map(|s| s.cta_selected).unwrap_or(false),
2014                #[cfg(feature = "xsd11")]
2015                assertion_outcome: None,
2016                ..SchemaInfo::empty()
2017            };
2018        }
2019        #[cfg(feature = "xsd11")]
2020        if cta_selected {
2021            let ev = self.validation_stack.last();
2022            let content_type = ev.and_then(|s| s.content_type);
2023            let validity = ev.map(|s| s.validity).unwrap_or(SchemaValidity::NotKnown);
2024            return SchemaInfo {
2025                schema_type,
2026                content_type,
2027                validity,
2028                type_source: ev.and_then(|s| s.type_source),
2029                cta_selected: true,
2030                assertion_outcome: None,
2031                ..SchemaInfo::empty()
2032            };
2033        }
2034        SchemaInfo::empty()
2035    }
2036
2037    /// Validate a text content event
2038    pub fn validate_text(&mut self, text: &str) {
2039        if !self.current_state.can_validate_text() {
2040            self.report_error(
2041                "cvc-complex-type",
2042                format!(
2043                    "Text content not allowed in current state {:?}",
2044                    self.current_state
2045                ),
2046            );
2047            return;
2048        }
2049
2050        // Collect errors first to avoid borrow conflicts
2051        let mut pending_errors: Vec<(&'static str, String)> = Vec::new();
2052        let has_non_ws = !text.trim().is_empty();
2053
2054        if let Some(ev_state) = self.validation_stack.last_mut() {
2055            // 1. Check content type
2056            if has_non_ws {
2057                match ev_state.content_type {
2058                    Some(ContentType::Empty) => {
2059                        let elem_name = self
2060                            .schema_set
2061                            .name_table
2062                            .resolve(ev_state.local_name)
2063                            .to_string();
2064                        pending_errors.push((
2065                            "cvc-complex-type.2.1",
2066                            format!(
2067                                "Element '{}' has empty content type but text was found",
2068                                elem_name,
2069                            ),
2070                        ));
2071                    }
2072                    Some(ContentType::ElementOnly) => {
2073                        let elem_name = self
2074                            .schema_set
2075                            .name_table
2076                            .resolve(ev_state.local_name)
2077                            .to_string();
2078                        pending_errors.push((
2079                            "cvc-complex-type.2.3",
2080                            format!(
2081                                "Element '{}' has element-only content but non-whitespace text was found",
2082                                elem_name,
2083                            ),
2084                        ));
2085                    }
2086                    _ => {}
2087                }
2088            }
2089
2090            // 2. Check xsi:nil — per cvc-elt.3.2.1 (XSD 1.1 §3.3.4.3),
2091            // a nilled element's children must be empty: no element info items
2092            // and no character info items. For mixed content this includes
2093            // whitespace, which is otherwise significant (only ignorable in
2094            // element-only content).
2095            if ev_state.is_nil
2096                && (has_non_ws || matches!(ev_state.content_type, Some(ContentType::Mixed)))
2097                && !text.is_empty()
2098            {
2099                let elem_name = self
2100                    .schema_set
2101                    .name_table
2102                    .resolve(ev_state.local_name)
2103                    .to_string();
2104                pending_errors.push((
2105                    "cvc-elt.3.2.1",
2106                    format!(
2107                        "Element '{}' is nilled but has non-empty content",
2108                        elem_name,
2109                    ),
2110                ));
2111            }
2112
2113            // 3. Accumulate text
2114            ev_state.text_content.push_str(text);
2115            ev_state.has_text = true;
2116        }
2117
2118        // Report collected errors
2119        for (constraint, message) in pending_errors {
2120            self.report_error(constraint, message);
2121        }
2122
2123        // Forward text to assertion fragment builder (XSD 1.1)
2124        #[cfg(feature = "xsd11")]
2125        if self.is_buffering_assertions() {
2126            if let Some(builder) = self.fragment_builder.as_mut() {
2127                builder.text(text);
2128            }
2129        }
2130
2131        self.current_state = ValidatorState::Text;
2132    }
2133
2134    /// Validate a whitespace-only text event
2135    ///
2136    /// Whitespace is always allowed in element-only content (it is insignificant).
2137    pub fn validate_whitespace(&mut self, text: &str) {
2138        if !self.current_state.can_validate_text() {
2139            self.report_error(
2140                "cvc-complex-type",
2141                format!(
2142                    "Whitespace not allowed in current state {:?}",
2143                    self.current_state
2144                ),
2145            );
2146            return;
2147        }
2148
2149        let mut nil_violation: Option<String> = None;
2150        let mut empty_violation: Option<String> = None;
2151        if let Some(ev_state) = self.validation_stack.last_mut() {
2152            // cvc-complex-type.2.1: when {content type} is empty the element
2153            // information item must have *no* character information items,
2154            // including whitespace. Element-only content still treats
2155            // whitespace as insignificant.
2156            if !text.is_empty() && matches!(ev_state.content_type, Some(ContentType::Empty)) {
2157                let elem_name = self
2158                    .schema_set
2159                    .name_table
2160                    .resolve(ev_state.local_name)
2161                    .to_string();
2162                empty_violation = Some(format!(
2163                    "Element '{}' has empty content type but text was found",
2164                    elem_name,
2165                ));
2166            }
2167
2168            // cvc-elt.3.2.1: a nilled element must have empty content. For
2169            // mixed and text-only content whitespace is significant, so any
2170            // text (including whitespace) is invalid. Element-only and empty
2171            // content treat whitespace as ignorable, so it stays accepted.
2172            if ev_state.is_nil
2173                && !text.is_empty()
2174                && matches!(
2175                    ev_state.content_type,
2176                    Some(ContentType::TextOnly) | Some(ContentType::Mixed)
2177                )
2178            {
2179                let elem_name = self
2180                    .schema_set
2181                    .name_table
2182                    .resolve(ev_state.local_name)
2183                    .to_string();
2184                nil_violation = Some(format!(
2185                    "Element '{}' is nilled but has non-empty content",
2186                    elem_name,
2187                ));
2188            }
2189
2190            // Accumulate (may be needed for TextOnly simple type validation)
2191            if matches!(
2192                ev_state.content_type,
2193                Some(ContentType::TextOnly) | Some(ContentType::Mixed)
2194            ) {
2195                ev_state.text_content.push_str(text);
2196                ev_state.has_text = true;
2197            }
2198        }
2199
2200        if let Some(msg) = empty_violation {
2201            self.report_error("cvc-complex-type.2.1", msg);
2202        }
2203        if let Some(msg) = nil_violation {
2204            self.report_error("cvc-elt.3.2.1", msg);
2205        }
2206
2207        // Forward whitespace to assertion fragment builder (XSD 1.1)
2208        #[cfg(feature = "xsd11")]
2209        if self.is_buffering_assertions() {
2210            if let Some(builder) = self.fragment_builder.as_mut() {
2211                builder.text(text);
2212            }
2213        }
2214
2215        self.current_state = ValidatorState::Whitespace;
2216    }
2217
2218    /// Validate an element end event
2219    pub fn validate_end_element(&mut self) -> SchemaInfo {
2220        if !self.current_state.can_end_element() {
2221            self.report_error(
2222                "cvc-complex-type",
2223                format!(
2224                    "End element not allowed in current state {:?}",
2225                    self.current_state
2226                ),
2227            );
2228            return SchemaInfo::invalid();
2229        }
2230
2231        let mut ev_state = match self.validation_stack.pop() {
2232            Some(s) => s,
2233            None => {
2234                self.report_error(
2235                    "cvc-complex-type",
2236                    "End element called but validation stack is empty",
2237                );
2238                return SchemaInfo::invalid();
2239            }
2240        };
2241
2242        // 1. Check content model completion
2243        if !ev_state.is_nil {
2244            match ev_state.content_type {
2245                Some(ContentType::ElementOnly) | Some(ContentType::Mixed) => {
2246                    if !ev_state.content_state.is_complete() {
2247                        let elem_name = self.schema_set.name_table.resolve(ev_state.local_name);
2248                        let err = errors::error(
2249                            "cvc-complex-type.2.4",
2250                            format!(
2251                                "Element '{}' content model is incomplete: expected more child elements",
2252                                elem_name,
2253                            ),
2254                            self.current_location.clone(),
2255                        );
2256                        self.report_validation_error_to(err, &mut ev_state.error_codes);
2257                        ev_state.validity = SchemaValidity::Invalid;
2258                    }
2259                }
2260                _ => {}
2261            }
2262        }
2263
2264        // 2. For TextOnly: validate text content against simple type
2265        if ev_state.content_type == Some(ContentType::TextOnly) && !ev_state.is_nil {
2266            // Handle default value before validation: if the element has no text
2267            // content and has a default, substitute the default value so that
2268            // simple-type validation runs against it (not the empty string).
2269            if let Some(elem_key) = ev_state.element_decl {
2270                let elem_data = &self.schema_set.arenas.elements[elem_key];
2271                if !ev_state.has_text && !ev_state.has_element_children {
2272                    if let Some(default_value) = &elem_data.default_value {
2273                        ev_state.is_default = true;
2274                        ev_state.text_content = default_value.clone();
2275                    } else if let Some(fixed_value) = &elem_data.fixed_value {
2276                        // XSD spec §3.3.4.3: "If fixed is specified, then the
2277                        // element's content must either be empty, in which case
2278                        // fixed behaves as default, or match fixed."
2279                        ev_state.text_content = fixed_value.clone();
2280                    }
2281                }
2282            }
2283
2284            if let Some(schema_type) = ev_state.schema_type {
2285                match super::simple::validate_simple_type(
2286                    &ev_state.text_content,
2287                    schema_type,
2288                    self.schema_set,
2289                ) {
2290                    Ok(result) => {
2291                        ev_state.member_type = result.member_type;
2292                        ev_state.typed_value = Some(result.typed_value);
2293                        ev_state.normalized_value = result.normalized_value;
2294
2295                        // QName/NOTATION prefix-binding check (Datatypes
2296                        // §3.3.18 / §3.3.19): the simple-type validator
2297                        // doesn't see namespaces, so an undeclared prefix
2298                        // (e.g. `xmlns:xsi` per W3C bug 4053) must be
2299                        // flagged here against the element's snapshot.
2300                        // Apply only to single (atomic) QName/NOTATION
2301                        // values — list-of-QName tokens are handled
2302                        // elsewhere and may legitimately use any prefix
2303                        // syntax in the joined text.
2304                        let needs_qname_check =
2305                            ev_state.typed_value.as_ref().is_some_and(|v| {
2306                                matches!(
2307                                    v.value,
2308                                    crate::types::value::XmlValueKind::Atomic(_)
2309                                ) && (v.type_code == XmlTypeCode::QName
2310                                    || v.type_code == XmlTypeCode::Notation)
2311                            });
2312                        if needs_qname_check && ev_state.validity == SchemaValidity::Valid {
2313                            if let Some(ctx) = ev_state.ns_context.as_ref() {
2314                                let raw = ev_state.text_content.trim();
2315                                if let Some(colon_pos) = raw.find(':') {
2316                                    let prefix = &raw[..colon_pos];
2317                                    let prefix_id = self.schema_set.name_table.add(prefix);
2318                                    if ctx.resolve_prefix(prefix_id).is_none() {
2319                                        let elem_name = self
2320                                            .schema_set
2321                                            .name_table
2322                                            .resolve(ev_state.local_name)
2323                                            .to_string();
2324                                        let err = errors::error(
2325                                            "cvc-datatype-valid.1.2.1",
2326                                            format!(
2327                                                "Element '{}' has QName value '{}' with undeclared prefix '{}'",
2328                                                elem_name, raw, prefix
2329                                            ),
2330                                            self.current_location.clone(),
2331                                        );
2332                                        self.report_validation_error_to(
2333                                            err,
2334                                            &mut ev_state.error_codes,
2335                                        );
2336                                        ev_state.validity = SchemaValidity::Invalid;
2337                                    }
2338                                }
2339                            }
2340                        }
2341                    }
2342                    Err(err) => {
2343                        self.report_validation_error_to(err, &mut ev_state.error_codes);
2344                        ev_state.validity = SchemaValidity::Invalid;
2345                    }
2346                }
2347            }
2348
2349            // Check fixed value on element — cvc-elt.5.2.2.2.2 (§3.3.4.3).
2350            // Use value-space comparison so that lexically-different but value-equivalent
2351            // forms (e.g. boolean "1" vs "true", float "1.0" vs "1.000", token whitespace)
2352            // are treated as matching. Reuses `ev_state.typed_value` parsed above
2353            // (line ~1778) to avoid a second parse of the same text content.
2354            if let Some(elem_key) = ev_state.element_decl {
2355                let elem_data = &self.schema_set.arenas.elements[elem_key];
2356                if let Some(fixed) = &elem_data.fixed_value {
2357                    let matches = if let Some(ref typed) = ev_state.typed_value {
2358                        super::simple::fixed_matches_typed(
2359                            &ev_state.text_content,
2360                            typed,
2361                            fixed,
2362                            ev_state.schema_type,
2363                            self.schema_set,
2364                        )
2365                    } else {
2366                        super::simple::fixed_values_equal(
2367                            &ev_state.text_content,
2368                            fixed,
2369                            ev_state.schema_type,
2370                            self.schema_set,
2371                        )
2372                    };
2373                    if !matches {
2374                        let elem_name = self.schema_set.name_table.resolve(ev_state.local_name);
2375                        let err = errors::error(
2376                            "cvc-elt.5.2.2",
2377                            format!(
2378                                "Element '{}' has fixed value '{}' but actual value is '{}'",
2379                                elem_name, fixed, ev_state.text_content,
2380                            ),
2381                            self.current_location.clone(),
2382                        );
2383                        self.report_validation_error_to(err, &mut ev_state.error_codes);
2384                        ev_state.validity = SchemaValidity::Invalid;
2385                    }
2386                }
2387            }
2388        }
2389
2390        // 2b. Fixed value check for mixed-content elements — cvc-elt.5.2.2.1 + 5.2.2.2.1 (§3.3.4.3).
2391        if ev_state.content_type == Some(ContentType::Mixed) && !ev_state.is_nil {
2392            if let Some(elem_key) = ev_state.element_decl {
2393                let elem_data = &self.schema_set.arenas.elements[elem_key];
2394                if let Some(ref fixed) = elem_data.fixed_value {
2395                    let elem_name = self.schema_set.name_table.resolve(ev_state.local_name);
2396                    if ev_state.has_element_children {
2397                        // cvc-elt.5.2.2.1: no element children when fixed value is present
2398                        let err = errors::error(
2399                            "cvc-elt.5.2.2.1",
2400                            format!(
2401                                "Element '{}' has fixed value '{}' but contains element children",
2402                                elem_name, fixed,
2403                            ),
2404                            self.current_location.clone(),
2405                        );
2406                        self.report_validation_error_to(err, &mut ev_state.error_codes);
2407                        ev_state.validity = SchemaValidity::Invalid;
2408                    } else if ev_state.has_text && ev_state.text_content != *fixed {
2409                        // cvc-elt.5.2.2.2.1: initial value (concatenated text) must match fixed
2410                        let err = errors::error(
2411                            "cvc-elt.5.2.2.2",
2412                            format!(
2413                                "Element '{}' has fixed value '{}' but actual value is '{}'",
2414                                elem_name, fixed, ev_state.text_content,
2415                            ),
2416                            self.current_location.clone(),
2417                        );
2418                        self.report_validation_error_to(err, &mut ev_state.error_codes);
2419                        ev_state.validity = SchemaValidity::Invalid;
2420                    }
2421                }
2422            }
2423        }
2424
2425        // 2c. Assertion evaluation hook (XSD 1.1)
2426        #[cfg(feature = "xsd11")]
2427        let type_has_assertions = matches!(ev_state.schema_type,
2428            Some(TypeKey::Complex(ct_key)) if has_inherited_assertions(ct_key, &self.schema_set.arenas));
2429        #[cfg(feature = "xsd11")]
2430        let mut assertion_outcome: Option<AssertionOutcome> = None;
2431
2432        #[cfg(feature = "xsd11")]
2433        'assertion_eval: {
2434            debug_assert!(
2435                self.flags.contains(ValidationFlags::PROCESS_ASSERTIONS)
2436                    == (self.assertion_source == AssertionSource::FragmentBuffer),
2437                "PROCESS_ASSERTIONS / AssertionSource invariant violated at end-element"
2438            );
2439
2440            if !self.is_buffering_assertions() {
2441                // Clean up orphan fragment_builder left by force_start when
2442                // the CTA element's type had no assertions and cta_switched
2443                // was false (so redetect_assertions_after_cta was never called).
2444                if self.fragment_builder.is_some() {
2445                    self.fragment_builder.take();
2446                    self.pending_assertion_frames.clear();
2447                    if let Some(arena) = self.fragment_arena.as_mut() {
2448                        arena.reset();
2449                    }
2450                }
2451                if type_has_assertions {
2452                    assertion_outcome = Some(AssertionOutcome::NotEvaluated);
2453                }
2454                break 'assertion_eval;
2455            }
2456
2457            // Forward end_element to builder
2458            if let Some(builder) = self.fragment_builder.as_mut() {
2459                if let Err(e) = builder.end_element() {
2460                    self.abort_assertion_buffer_op("end_element", e);
2461                    ev_state.validity = SchemaValidity::Invalid;
2462                    break 'assertion_eval;
2463                }
2464            }
2465
2466            if ev_state.owns_assertion_buffer {
2467                // Pop the assertion frame for this element
2468                let frame = match self.assertion_buffer_stack.pop() {
2469                    Some(f) => f,
2470                    None => {
2471                        // Should not happen, but don't panic in validation
2472                        self.abort_assertion_buffering(
2473                            "Internal: assertion buffer stack underflow".into(),
2474                        );
2475                        ev_state.validity = SchemaValidity::Invalid;
2476                        break 'assertion_eval;
2477                    }
2478                };
2479
2480                if self.assertion_buffer_stack.is_empty() {
2481                    // Outermost asserted element closes — finalize and evaluate
2482                    let builder = match self.fragment_builder.take() {
2483                        Some(b) => b,
2484                        None => {
2485                            // builder was already taken (should not happen)
2486                            self.pending_assertion_frames.clear();
2487                            if let Some(arena) = self.fragment_arena.as_mut() {
2488                                arena.reset();
2489                            }
2490                            break 'assertion_eval;
2491                        }
2492                    };
2493                    match builder.finalize() {
2494                        Ok(doc) => {
2495                            // Evaluate nested (deferred) frames first
2496                            let pending = std::mem::take(&mut self.pending_assertion_frames);
2497                            for pf in &pending {
2498                                let errs = evaluate_complex_type_assertions(
2499                                    &doc,
2500                                    pf.element_ref,
2501                                    pf.complex_type_key,
2502                                    self.schema_set,
2503                                );
2504                                self.report_assertion_errors_deferred(
2505                                    errs,
2506                                    &pf.element_path,
2507                                    &pf.location,
2508                                );
2509                            }
2510                            // Evaluate outermost frame (current element_path is valid)
2511                            let errs = evaluate_complex_type_assertions(
2512                                &doc,
2513                                frame.element_ref,
2514                                frame.complex_type_key,
2515                                self.schema_set,
2516                            );
2517                            if errs.is_empty() {
2518                                assertion_outcome = Some(AssertionOutcome::Passed);
2519                            } else {
2520                                assertion_outcome = Some(AssertionOutcome::Failed);
2521                            }
2522                            self.report_assertion_errors(errs, &mut ev_state);
2523                        }
2524                        Err(e) => {
2525                            // Clear stale deferred frames — they reference
2526                            // nodes in the failed document and must not leak
2527                            // into the next buffered subtree.
2528                            self.pending_assertion_frames.clear();
2529                            let err = errors::error(
2530                                "cvc-assertion",
2531                                format!("Failed to finalize assertion fragment: {}", e),
2532                                self.current_location.clone(),
2533                            );
2534                            self.report_validation_error_to(err, &mut ev_state.error_codes);
2535                            ev_state.validity = SchemaValidity::Invalid;
2536                            assertion_outcome = Some(AssertionOutcome::Failed);
2537                        }
2538                    }
2539                    // Reset arena for reuse
2540                    if let Some(arena) = self.fragment_arena.as_mut() {
2541                        arena.reset();
2542                    }
2543                } else {
2544                    // Nested asserted element — defer to outermost close
2545                    let mut deferred = frame;
2546                    deferred.element_path = self.element_path.clone();
2547                    deferred.location = self.current_location.clone();
2548                    self.pending_assertion_frames.push(deferred);
2549                    assertion_outcome = Some(AssertionOutcome::NotEvaluated);
2550                }
2551            }
2552        }
2553
2554        // Fallback: if assertion_outcome wasn't set but the type has assertions
2555        #[cfg(feature = "xsd11")]
2556        if assertion_outcome.is_none() && type_has_assertions {
2557            assertion_outcome = Some(AssertionOutcome::NotEvaluated);
2558        }
2559
2560        // 3. Identity constraint processing (field values + scope exit + keyref cross-ref)
2561        let is_complex_content = matches!(
2562            ev_state.content_type,
2563            Some(ContentType::ElementOnly) | Some(ContentType::Mixed)
2564        );
2565        // Resolve QName/NOTATION typed values for IC comparison (namespace-aware).
2566        // Use a separate copy to preserve the original PSVI typed_value.
2567        let ic_typed_value = Self::resolve_ic_qname_value(
2568            &ev_state.typed_value,
2569            &ev_state.text_content,
2570            ev_state.ns_context.as_ref(),
2571            &self.schema_set.name_table,
2572        );
2573        let ic_ref = ic_typed_value.as_ref().or(ev_state.typed_value.as_ref());
2574        self.process_constraints_end_element(
2575            &ev_state.text_content,
2576            ic_ref,
2577            ev_state.is_nil,
2578            is_complex_content,
2579            &mut ev_state.error_codes,
2580        );
2581
2582        // 3b. Pop scope table and propagate key/unique tables upward to parent
2583        if let Some(Some(scope_map)) = self.ic_scope_tables.pop() {
2584            if let Some(parent_slot) = self.ic_scope_tables.last_mut() {
2585                let parent_map = parent_slot.get_or_insert_with(HashMap::new);
2586                for (ic_key, key_table) in scope_map {
2587                    parent_map
2588                        .entry(ic_key)
2589                        .and_modify(|existing| {
2590                            existing.sequences.extend(key_table.sequences.clone())
2591                        })
2592                        .or_insert(key_table);
2593                }
2594            } else {
2595                // Root element — save for public access via identity_constraint_tables()
2596                self.final_ic_tables = Some(scope_map);
2597            }
2598        }
2599
2600        // 3c. Retry deferred keyrefs against enriched parent scope
2601        if !self.deferred_keyrefs.is_empty() {
2602            let pending = std::mem::take(&mut self.deferred_keyrefs);
2603            let name_table = &self.schema_set.name_table;
2604            let scope_empty = self.ic_scope_tables.is_empty();
2605            let mut still_deferred = Vec::new();
2606            let mut deferred_errors = Vec::new();
2607            for (keyref_table, refer_key) in pending {
2608                let target = refer_key.and_then(|rk| {
2609                    self.ic_scope_tables
2610                        .last()
2611                        .and_then(|slot| slot.as_ref())
2612                        .and_then(|map| map.get(&rk))
2613                });
2614                match target {
2615                    Some(target_table) => {
2616                        let errs = keyref_table.check_keyref_against(target_table, name_table);
2617                        deferred_errors.extend(errs);
2618                    }
2619                    None => {
2620                        if scope_empty {
2621                            // Root element closed — no more ancestors to try
2622                            let keyref_name = name_table.resolve(keyref_table.constraint_name);
2623                            let refer_display = self
2624                                .compiled_constraints
2625                                .get(&keyref_table.ic_key)
2626                                .and_then(|opt| opt.as_ref())
2627                                .and_then(|compiled| {
2628                                    compiled.refer.as_ref().map(|refer| {
2629                                        let refer_ns =
2630                                            refer.namespace.or(compiled.target_namespace);
2631                                        format_resolved_qname(
2632                                            name_table,
2633                                            refer_ns,
2634                                            refer.local_name,
2635                                        )
2636                                    })
2637                                })
2638                                .unwrap_or_else(|| "<unknown>".to_string());
2639                            deferred_errors.push(errors::error(
2640                                "cvc-identity-constraint.4.3",
2641                                format!(
2642                                    "Keyref '{}' references unknown constraint '{}'",
2643                                    keyref_name, refer_display
2644                                ),
2645                                None,
2646                            ));
2647                        } else {
2648                            still_deferred.push((keyref_table, refer_key));
2649                        }
2650                    }
2651                }
2652            }
2653            self.deferred_keyrefs = still_deferred;
2654            // Emit deferred errors directly through the sink (not emit_error) because
2655            // the original keyref element has already been popped from validation_stack.
2656            // Using emit_error() would misattribute the error to the current ancestor.
2657            for err in deferred_errors {
2658                self.sink.on_error(err);
2659            }
2660        }
2661
2662        // 4. ID/IDREF collection from element text content.
2663        // Owner is the parent element per §3.17.5.2: the binding is to the
2664        // element that has the ID-typed child in its [children].
2665        // ev_state is the popped child; the parent is now the stack top.
2666        if let Some(ref tv) = ev_state.typed_value {
2667            let parent_serial = self
2668                .validation_stack
2669                .last()
2670                .map(|e| e.element_serial)
2671                .unwrap_or(ev_state.element_serial); // root: no parent, use self
2672            self.collect_id_idref(tv, &ev_state.text_content, parent_serial);
2673            self.check_entity_declared(tv);
2674        }
2675
2676        // 5. Update element path
2677        self.pop_element_path();
2678
2679        let validity = ev_state.validity;
2680        self.current_state = ValidatorState::EndElement;
2681
2682        // Compute [validation attempted] per spec §3.3.5.1
2683        let all_full = !ev_state.any_child_not_full && !ev_state.any_attr_not_full;
2684        let all_none = !ev_state.any_child_not_none && !ev_state.any_attr_not_none;
2685        let validation_attempted = if ev_state.strictly_assessed && all_full {
2686            ValidationAttempted::Full
2687        } else if !ev_state.strictly_assessed && all_none {
2688            ValidationAttempted::None
2689        } else {
2690            ValidationAttempted::Partial
2691        };
2692
2693        // Propagate to parent
2694        if let Some(parent) = self.validation_stack.last_mut() {
2695            match validation_attempted {
2696                ValidationAttempted::Full => {
2697                    parent.any_child_not_none = true;
2698                }
2699                ValidationAttempted::None => {
2700                    parent.any_child_not_full = true;
2701                }
2702                ValidationAttempted::Partial => {
2703                    parent.any_child_not_full = true;
2704                    parent.any_child_not_none = true;
2705                }
2706            }
2707        }
2708
2709        SchemaInfo {
2710            element_decl: ev_state.element_decl,
2711            attribute_decl: None,
2712            schema_type: ev_state.schema_type,
2713            member_type: ev_state.member_type,
2714            validity,
2715            validation_attempted,
2716            is_default: ev_state.is_default,
2717            is_nil: ev_state.is_nil,
2718            content_type: ev_state.content_type,
2719            typed_value: ev_state.typed_value,
2720            normalized_value: ev_state.normalized_value,
2721            schema_error_codes: ev_state.error_codes,
2722            notation: ev_state.notation,
2723            deferred_by_cta: false,
2724            type_source: ev_state.type_source,
2725            #[cfg(feature = "xsd11")]
2726            cta_selected: ev_state.cta_selected,
2727            #[cfg(feature = "xsd11")]
2728            assertion_outcome,
2729        }
2730    }
2731
2732    /// Finalize validation
2733    ///
2734    /// Checks that the validation stack is empty and performs IDREF validation.
2735    pub fn end_validation(&mut self) -> Result<(), ValidationError> {
2736        if !self.current_state.can_finish() {
2737            return Err(errors::error(
2738                "cvc-complex-type",
2739                format!(
2740                    "end_validation called in invalid state {:?}",
2741                    self.current_state
2742                ),
2743                self.current_location.clone(),
2744            ));
2745        }
2746
2747        if !self.validation_stack.is_empty() {
2748            return Err(errors::error(
2749                "cvc-complex-type",
2750                format!(
2751                    "Validation ended with {} unclosed elements",
2752                    self.validation_stack.len()
2753                ),
2754                self.current_location.clone(),
2755            ));
2756        }
2757
2758        // IDREF validation (cvc-id.1): check all pending IDREFs resolve
2759        for (idref_value, location, element_path) in &self.pending_idrefs {
2760            if !self.id_values.contains_key(idref_value) {
2761                self.sink.on_error(errors::error_with_path(
2762                    "cvc-id.1",
2763                    format!(
2764                        "IDREF '{}' does not match any ID in the document",
2765                        idref_value
2766                    ),
2767                    location.clone(),
2768                    element_path,
2769                ));
2770            }
2771        }
2772
2773        #[cfg(feature = "xsd11")]
2774        {
2775            debug_assert!(
2776                self.assertion_buffer_stack.is_empty(),
2777                "assertion_buffer_stack not empty at end_validation"
2778            );
2779            debug_assert!(
2780                self.fragment_builder.is_none(),
2781                "fragment_builder not None at end_validation"
2782            );
2783            debug_assert!(
2784                self.pending_assertion_frames.is_empty(),
2785                "pending_assertion_frames not empty at end_validation"
2786            );
2787        }
2788
2789        self.current_state = ValidatorState::Finish;
2790        Ok(())
2791    }
2792
2793    // -----------------------------------------------------------------------
2794    // Query API
2795    // -----------------------------------------------------------------------
2796
2797    /// Get elements expected at the current position in the content model
2798    pub fn get_expected_elements(&self) -> Vec<ExpectedElement> {
2799        let ev_state = match self.validation_stack.last() {
2800            Some(s) => s,
2801            None => return Vec::new(),
2802        };
2803
2804        match &ev_state.content_state {
2805            ContentValidatorState::Nfa {
2806                nfa, active_states, ..
2807            } => active_states
2808                .expected_element_terms(nfa)
2809                .into_iter()
2810                .map(|(name, namespace, element_key)| ExpectedElement {
2811                    local_name: name,
2812                    namespace,
2813                    element_key,
2814                })
2815                .collect(),
2816            ContentValidatorState::AllGroup { model, state, .. } => {
2817                let mut result = Vec::new();
2818                for (i, particle) in model.particles.iter().enumerate() {
2819                    if state.can_accept(model, i) {
2820                        if let crate::compiler::NfaTerm::Element {
2821                            ref name,
2822                            ref namespace,
2823                            ref element_key,
2824                            ..
2825                        } = particle.term
2826                        {
2827                            result.push(ExpectedElement {
2828                                local_name: *name,
2829                                namespace: *namespace,
2830                                element_key: *element_key,
2831                            });
2832                        }
2833                    }
2834                }
2835                result
2836            }
2837            #[cfg(feature = "xsd11")]
2838            ContentValidatorState::AllGroupExtension {
2839                model,
2840                state,
2841                extension_nfa,
2842                phase,
2843            } => {
2844                use super::content::AllGroupExtPhase;
2845
2846                let mut result = Vec::new();
2847                match phase {
2848                    AllGroupExtPhase::AllGroup => {
2849                        // Include acceptable all-group particles
2850                        for (i, particle) in model.particles.iter().enumerate() {
2851                            if state.can_accept(model, i) {
2852                                if let crate::compiler::NfaTerm::Element {
2853                                    ref name,
2854                                    ref namespace,
2855                                    ref element_key,
2856                                    ..
2857                                } = particle.term
2858                                {
2859                                    result.push(ExpectedElement {
2860                                        local_name: *name,
2861                                        namespace: *namespace,
2862                                        element_key: *element_key,
2863                                    });
2864                                }
2865                            }
2866                        }
2867                        // If all-group is satisfied, also include extension NFA elements
2868                        if state.is_satisfied(model) {
2869                            let initial = crate::compiler::ActiveStates::from_nfa(extension_nfa);
2870                            for (name, namespace, element_key) in
2871                                initial.expected_element_terms(extension_nfa)
2872                            {
2873                                result.push(ExpectedElement {
2874                                    local_name: name,
2875                                    namespace,
2876                                    element_key,
2877                                });
2878                            }
2879                        }
2880                    }
2881                    AllGroupExtPhase::Nfa(active_states) => {
2882                        for (name, namespace, element_key) in
2883                            active_states.expected_element_terms(extension_nfa)
2884                        {
2885                            result.push(ExpectedElement {
2886                                local_name: name,
2887                                namespace,
2888                                element_key,
2889                            });
2890                        }
2891                    }
2892                }
2893                result
2894            }
2895            _ => Vec::new(),
2896        }
2897    }
2898
2899    /// Get attributes expected/allowed for the current element
2900    pub fn get_expected_attributes(&self) -> Vec<ExpectedAttribute> {
2901        let ev_state = match self.validation_stack.last() {
2902            Some(s) => s,
2903            None => return Vec::new(),
2904        };
2905
2906        let ct_key = match ev_state.schema_type {
2907            Some(TypeKey::Complex(ct)) => ct,
2908            _ => return Vec::new(),
2909        };
2910
2911        let ct_data = &self.schema_set.arenas.complex_types[ct_key];
2912        let mut result = Vec::new();
2913
2914        for (i, attr_use) in ct_data.attributes.iter().enumerate() {
2915            let use_kind = attr_use.use_kind;
2916            if use_kind == AttributeUseKind::Prohibited {
2917                continue;
2918            }
2919            let resolved = ct_data.resolved_attributes.get(i);
2920            let (attr_name, attr_ns) =
2921                self.resolve_attr_use_name_ns(attr_use, resolved, ct_data.target_namespace);
2922            let attr_key = resolved.and_then(|r| r.resolved_ref);
2923
2924            result.push(ExpectedAttribute {
2925                local_name: attr_name,
2926                namespace: attr_ns,
2927                attribute_key: attr_key,
2928                required: use_kind == AttributeUseKind::Required,
2929            });
2930        }
2931
2932        // Include attributes from attribute groups
2933        for ga in self.collect_group_attributes(ct_data) {
2934            if ga.use_kind == AttributeUseKind::Prohibited {
2935                continue;
2936            }
2937            result.push(ExpectedAttribute {
2938                local_name: ga.name,
2939                namespace: ga.namespace,
2940                attribute_key: ga.attr_key,
2941                required: ga.use_kind == AttributeUseKind::Required,
2942            });
2943        }
2944
2945        result
2946    }
2947
2948    /// Get default attributes that should be added to the current element
2949    pub fn get_default_attributes(&self) -> Vec<DefaultAttribute> {
2950        let ev_state = match self.validation_stack.last() {
2951            Some(s) => s,
2952            None => return Vec::new(),
2953        };
2954
2955        let ct_key = match ev_state.schema_type {
2956            Some(TypeKey::Complex(ct)) => ct,
2957            _ => return Vec::new(),
2958        };
2959
2960        let ct_data = &self.schema_set.arenas.complex_types[ct_key];
2961        let mut result = Vec::new();
2962
2963        for (i, attr_use) in ct_data.attributes.iter().enumerate() {
2964            if attr_use.use_kind == AttributeUseKind::Prohibited {
2965                continue;
2966            }
2967
2968            let resolved = ct_data.resolved_attributes.get(i);
2969            let (attr_name, attr_ns) =
2970                self.resolve_attr_use_name_ns(attr_use, resolved, ct_data.target_namespace);
2971            let attr_key = resolved.and_then(|r| r.resolved_ref);
2972
2973            // Skip if already provided
2974            if ev_state.seen_attributes.contains(&(attr_ns, attr_name)) {
2975                continue;
2976            }
2977
2978            // Check for default value — first on the use, then on the global decl
2979            let default = attr_use.attribute.default_value.as_deref().or_else(|| {
2980                attr_key
2981                    .and_then(|k| self.schema_set.arenas.attributes.get(k))
2982                    .and_then(|d| d.default_value.as_deref())
2983            });
2984            if let Some(value) = default {
2985                if let Some(attr_key) = attr_key {
2986                    result.push(DefaultAttribute {
2987                        local_name: attr_name,
2988                        namespace: attr_ns,
2989                        attribute_key: attr_key,
2990                        value: value.to_string(),
2991                    });
2992                }
2993            }
2994        }
2995
2996        // Include defaults from attribute groups
2997        for ga in self.collect_group_attributes(ct_data) {
2998            if ga.use_kind == AttributeUseKind::Prohibited {
2999                continue;
3000            }
3001            if ev_state.seen_attributes.contains(&(ga.namespace, ga.name)) {
3002                continue;
3003            }
3004            if let Some(value) = ga.default_value {
3005                if let Some(attr_key) = ga.attr_key {
3006                    result.push(DefaultAttribute {
3007                        local_name: ga.name,
3008                        namespace: ga.namespace,
3009                        attribute_key: attr_key,
3010                        value,
3011                    });
3012                }
3013            }
3014        }
3015
3016        result
3017    }
3018
3019    /// Get the `[inherited attributes]` PSVI property for the current element
3020    /// (XSD 1.1 §3.3.5.6, structures.html line 5200).
3021    ///
3022    /// Returns the frozen `incoming_inherited` snapshot — ancestor-owned
3023    /// potentially-inherited attributes with nearest-owner shadowing.
3024    /// This is the spec's `[inherited attributes]` property; it is NOT
3025    /// filtered by the element's own `[attributes]`.
3026    ///
3027    /// Returns empty for skipped elements.
3028    #[cfg(feature = "xsd11")]
3029    pub fn get_inherited_attributes(&self) -> Vec<InheritedAttribute> {
3030        let ev_state = match self.validation_stack.last() {
3031            Some(s) => s,
3032            None => return Vec::new(),
3033        };
3034
3035        // §3.3.5.6: [inherited attributes] is only defined for non-skipped elements
3036        if ev_state.process_contents == ContentProcessing::Skip {
3037            return Vec::new();
3038        }
3039
3040        let mut result = Vec::new();
3041        for ((ns, name), val) in &ev_state.incoming_inherited {
3042            result.push(InheritedAttribute {
3043                local_name: *name,
3044                namespace: *ns,
3045                attribute_key: val.attribute_key,
3046                value: val.value.clone(),
3047            });
3048        }
3049        result
3050    }
3051
3052    /// Get the content processing mode for the current element
3053    pub fn content_processing(&self) -> ContentProcessing {
3054        self.validation_stack
3055            .last()
3056            .map(|s| s.process_contents)
3057            .unwrap_or(ContentProcessing::Strict)
3058    }
3059
3060    // -----------------------------------------------------------------------
3061    // Internal helpers
3062    // -----------------------------------------------------------------------
3063
3064    /// Push a new element onto the validation stack and update the element path
3065    fn push_element(&mut self, mut ev_state: ElementValidationState) {
3066        // Assign a unique serial for ID/IDREF owner-element binding (§3.17.5.2)
3067        ev_state.element_serial = self.next_element_serial;
3068        self.next_element_serial += 1;
3069
3070        // Record the first element using this namespace (XSD 1.0 §4.3.2 Rule 4
3071        // "late-arriving components"). Gated to XSD 1.0; XSD 1.1 §G.1.15
3072        // relaxes the rule entirely, so the map stays empty there.
3073        if self.schema_set.is_xsd10() {
3074            self.first_namespace_use
3075                .entry(ev_state.namespace)
3076                .or_insert(ev_state.element_serial);
3077        }
3078
3079        let local_name = self.schema_set.name_table.resolve(ev_state.local_name);
3080        if !self.element_path.is_empty() || self.validation_stack.is_empty() {
3081            self.element_path.push('/');
3082        }
3083        self.element_path.push_str(&local_name);
3084
3085        // Inherit base URI from parent element, or from the document-level
3086        // base URI for the root element.
3087        ev_state.base_uri = match self.validation_stack.last() {
3088            Some(parent) => parent.base_uri.clone(),
3089            None => self.instance_base_uri.clone(),
3090        };
3091        ev_state.schema_location_hint_start = self.schema_location_hints.len();
3092        ev_state.no_namespace_schema_location_hint_start =
3093            self.no_namespace_schema_location_hints.len();
3094
3095        self.validation_stack.push(ev_state);
3096        self.ic_scope_tables.push(None);
3097
3098        // Propagate inherited attributes from parent to child (XSD 1.1).
3099        // §3.3.5.6: [inherited attributes] only exists when the parent is
3100        // strictly/laxly assessed and the child is not attributed to a skip
3101        // wildcard.
3102        //
3103        // Parent's outgoing_inherited becomes the child's incoming_inherited
3104        // (frozen PSVI view) and outgoing_inherited (mutable for this
3105        // element's own inheritable attrs to shadow).
3106        #[cfg(feature = "xsd11")]
3107        {
3108            let len = self.validation_stack.len();
3109            if len >= 2 {
3110                let parent_pc = self.validation_stack[len - 2].process_contents;
3111                let child_pc = self.validation_stack[len - 1].process_contents;
3112                if parent_pc != ContentProcessing::Skip && child_pc != ContentProcessing::Skip {
3113                    let from_parent = self.validation_stack[len - 2].outgoing_inherited.clone();
3114                    self.validation_stack[len - 1].incoming_inherited = from_parent.clone();
3115                    self.validation_stack[len - 1].outgoing_inherited = from_parent;
3116                }
3117            }
3118        }
3119
3120        if self.current_state == ValidatorState::None {
3121            self.current_state = ValidatorState::Start;
3122        }
3123        self.current_state = ValidatorState::Element;
3124    }
3125
3126    /// Pop the last element from the element path
3127    fn pop_element_path(&mut self) {
3128        if let Some(pos) = self.element_path.rfind('/') {
3129            self.element_path.truncate(pos);
3130        } else {
3131            self.element_path.clear();
3132        }
3133    }
3134
3135    /// Emit a validation error: record its constraint code on the current
3136    /// element's `error_codes` list and dispatch to the sink.
3137    fn emit_error(&mut self, err: ValidationError) {
3138        if let Some(ev) = self.validation_stack.last_mut() {
3139            ev.error_codes.push(err.constraint);
3140        }
3141        self.sink.on_error(err);
3142    }
3143
3144    /// Emit a validation error recording the code on an explicit target
3145    /// instead of the stack top. Used during `validate_end_element` where
3146    /// the element has already been popped.
3147    fn emit_error_to(&mut self, err: ValidationError, codes: &mut Vec<&'static str>) {
3148        codes.push(err.constraint);
3149        self.sink.on_error(err);
3150    }
3151
3152    /// Report a validation error through the sink
3153    fn report_error(&mut self, constraint: &'static str, message: impl Into<String>) {
3154        let err = errors::error(constraint, message, self.current_location.clone());
3155        let err = if self.element_path.is_empty() {
3156            err
3157        } else {
3158            err.with_path(self.element_path.clone())
3159        };
3160        self.emit_error(err);
3161    }
3162
3163    /// Enrich `err` with `current_location` and `element_path` (if either is set).
3164    /// `with_location` / `with_path` overwrite so this is safe to call even when
3165    /// the error already carries them.
3166    fn enrich(&self, err: ValidationError) -> ValidationError {
3167        let err = match &self.current_location {
3168            Some(loc) => err.with_location(loc.clone()),
3169            None => err,
3170        };
3171        if self.element_path.is_empty() {
3172            err
3173        } else {
3174            err.with_path(self.element_path.clone())
3175        }
3176    }
3177
3178    /// Enrich `err` with current location/path and report it to an explicit
3179    /// `codes` target (e.g. during `validate_end_element` after the element
3180    /// has been popped off the stack).
3181    fn report_validation_error_to(&mut self, err: ValidationError, codes: &mut Vec<&'static str>) {
3182        let err = self.enrich(err);
3183        self.emit_error_to(err, codes);
3184    }
3185
3186    /// Enrich an existing `ValidationError` with location/path and report it.
3187    fn report_validation_error(&mut self, err: ValidationError) {
3188        let err = self.enrich(err);
3189        self.emit_error(err);
3190    }
3191
3192    /// Mark the current element as invalid.
3193    fn mark_current_invalid(&mut self) {
3194        if let Some(s) = self.validation_stack.last_mut() {
3195            s.validity = SchemaValidity::Invalid;
3196        }
3197    }
3198
3199    /// Get the effective base URI for the current element (or the document
3200    /// base URI if no element is on the stack).
3201    fn current_element_base_uri(&self) -> String {
3202        self.validation_stack
3203            .last()
3204            .map(|ev| ev.base_uri.clone())
3205            .unwrap_or_else(|| self.instance_base_uri.clone())
3206    }
3207
3208    fn rebase_hint_range(&mut self, sl_start: usize, nnsl_start: usize, base_uri: &str) {
3209        for hint in &mut self.schema_location_hints[sl_start..] {
3210            hint.base_uri = base_uri.to_string();
3211        }
3212        for hint in &mut self.no_namespace_schema_location_hints[nnsl_start..] {
3213            hint.base_uri = base_uri.to_string();
3214        }
3215    }
3216
3217    // -----------------------------------------------------------------------
3218    // XSI built-in attribute helpers
3219    // -----------------------------------------------------------------------
3220
3221    /// Dispatch validation for an `xsi:*` attribute.
3222    ///
3223    /// The four built-in XSI attributes (`type`, `nil`, `schemaLocation`,
3224    /// `noNamespaceSchemaLocation`) are validated against their spec-defined
3225    /// types. Unknown `xsi:*` attributes fall through to normal
3226    /// attribute/wildcard validation.
3227    fn validate_xsi_attribute(&mut self, local_name: NameId, value: &str) -> SchemaInfo {
3228        let builtin = self.schema_set.builtin_types();
3229        if local_name == well_known::XSI_TYPE {
3230            // Attribute-level: lexical xs:QName validation only.
3231            // Semantic resolution (cvc-elt.4.1) is handled in validate_element_by_id.
3232            let type_key = TypeKey::Simple(builtin.qname);
3233            let attr_key = builtin.xsi_type_attr;
3234            return self.validate_xsi_simple_value(value, type_key, attr_key);
3235        }
3236        if local_name == well_known::XSI_NIL {
3237            let type_key = TypeKey::Simple(builtin.boolean);
3238            let attr_key = builtin.xsi_nil_attr;
3239            return self.validate_xsi_simple_value(value, type_key, attr_key);
3240        }
3241        if local_name == well_known::XSI_SCHEMA_LOCATION {
3242            return self.validate_xsi_schema_location(value);
3243        }
3244        if local_name == well_known::XSI_NO_NAMESPACE_SCHEMA_LOCATION {
3245            return self.validate_xsi_no_ns_schema_location(value);
3246        }
3247        // Unknown xsi:* — fall through to normal attribute/wildcard validation
3248        self.validate_unknown_xsi_attribute(local_name, value)
3249    }
3250
3251    /// Unknown `xsi:*` attributes go through normal wildcard validation.
3252    fn validate_unknown_xsi_attribute(&mut self, local_name: NameId, value: &str) -> SchemaInfo {
3253        let ct_key = match self.validation_stack.last() {
3254            Some(ev) => match ev.schema_type {
3255                Some(TypeKey::Complex(ct)) => ct,
3256                _ => return SchemaInfo::empty(),
3257            },
3258            None => return SchemaInfo::empty(),
3259        };
3260        self.validate_attribute_against_type(
3261            ct_key,
3262            local_name,
3263            Some(well_known::XSI_NAMESPACE),
3264            value,
3265        )
3266    }
3267
3268    /// Validate a value against a built-in simple type and return attribute `SchemaInfo`.
3269    fn validate_xsi_simple_value(
3270        &mut self,
3271        value: &str,
3272        type_key: TypeKey,
3273        attr_key: AttributeKey,
3274    ) -> SchemaInfo {
3275        match super::simple::validate_simple_type(value, type_key, self.schema_set) {
3276            Ok(result) => SchemaInfo {
3277                element_decl: None,
3278                attribute_decl: Some(attr_key),
3279                schema_type: Some(type_key),
3280                member_type: result.member_type,
3281                validity: SchemaValidity::Valid,
3282                validation_attempted: ValidationAttempted::Full,
3283                is_default: false,
3284                is_nil: false,
3285                content_type: None,
3286                typed_value: Some(result.typed_value),
3287                normalized_value: result.normalized_value,
3288                schema_error_codes: Vec::new(),
3289                notation: None,
3290                deferred_by_cta: false,
3291                type_source: None,
3292                #[cfg(feature = "xsd11")]
3293                cta_selected: false,
3294                #[cfg(feature = "xsd11")]
3295                assertion_outcome: None,
3296            },
3297            Err(err) => {
3298                self.report_validation_error(err);
3299                SchemaInfo {
3300                    attribute_decl: Some(attr_key),
3301                    schema_type: Some(type_key),
3302                    validity: SchemaValidity::Invalid,
3303                    validation_attempted: ValidationAttempted::Full,
3304                    ..SchemaInfo::empty()
3305                }
3306            }
3307        }
3308    }
3309
3310    /// Validate `xsi:schemaLocation` as a whitespace-separated list of anyURI
3311    /// tokens with even token count.
3312    fn validate_xsi_schema_location(&mut self, value: &str) -> SchemaInfo {
3313        let builtin = self.schema_set.builtin_types();
3314        let any_uri_key = TypeKey::Simple(builtin.any_uri);
3315        let list_type_key = TypeKey::Simple(builtin.xsi_schema_location_type);
3316        let attr_key = builtin.xsi_schema_location_attr;
3317
3318        let tokens: Vec<&str> = value.split_whitespace().collect();
3319        let mut validity = SchemaValidity::Valid;
3320
3321        // Even token count check (namespace/location pairs)
3322        if !tokens.len().is_multiple_of(2) {
3323            self.report_error(
3324                "cvc-schema-location",
3325                format!(
3326                    "xsi:schemaLocation value must contain an even number of URI tokens, \
3327                     but found {} tokens",
3328                    tokens.len()
3329                ),
3330            );
3331            validity = SchemaValidity::Invalid;
3332        }
3333
3334        // Validate each token as xs:anyURI
3335        for token in &tokens {
3336            if let Err(err) =
3337                super::simple::validate_simple_type(token, any_uri_key, self.schema_set)
3338            {
3339                self.report_validation_error(err);
3340                validity = SchemaValidity::Invalid;
3341            }
3342        }
3343
3344        // Accumulate namespace/location pairs (even from invalid attributes —
3345        // the complete pairs are still valid hints).
3346        //
3347        // XSD 1.0 §4.3.2 Rule 4 ("late-arriving components") is enforced
3348        // here: a pair that announces a *new* schema document for a
3349        // namespace whose first item already appeared earlier in the
3350        // instance document is rejected. Re-announcing the same schema
3351        // document (same location) for an already-seen namespace is
3352        // allowed — no new components are introduced. XSD 1.1 §G.1.15
3353        // relaxes the rule entirely (gated by `is_xsd10()`).
3354        let base_uri = self.current_element_base_uri();
3355        let xsd10 = self.schema_set.is_xsd10();
3356        let current_serial = self.validation_stack.last().map(|ev| ev.element_serial);
3357        for pair in tokens.chunks_exact(2) {
3358            let ns_str = pair[0];
3359            let loc_str = pair[1];
3360
3361            if xsd10 {
3362                if let Some(serial) = current_serial {
3363                    let ns_id: Option<NameId> = if ns_str.is_empty() {
3364                        None
3365                    } else {
3366                        Some(self.schema_set.name_table.add(ns_str))
3367                    };
3368                    if let Some(&first_serial) = self.first_namespace_use.get(&ns_id) {
3369                        if first_serial != serial
3370                            && !self
3371                                .schema_location_hints
3372                                .iter()
3373                                .any(|h| h.namespace == ns_str && h.location == loc_str)
3374                        {
3375                            self.report_error(
3376                                "sch-late-component",
3377                                format!(
3378                                    "xsi:schemaLocation announces a new schema document \
3379                                     '{loc_str}' for namespace '{ns_str}', but an item \
3380                                     from that namespace has already been encountered \
3381                                     earlier in the instance document (XSD 1.0 §4.3.2 \
3382                                     Rule 4 'late-arriving components')"
3383                                ),
3384                            );
3385                            validity = SchemaValidity::Invalid;
3386                        }
3387                    }
3388                }
3389            }
3390
3391            self.schema_location_hints.push(SchemaLocationHint {
3392                namespace: ns_str.to_string(),
3393                location: loc_str.to_string(),
3394                base_uri: base_uri.clone(),
3395            });
3396        }
3397
3398        SchemaInfo {
3399            element_decl: None,
3400            attribute_decl: Some(attr_key),
3401            schema_type: Some(list_type_key),
3402            member_type: None,
3403            validity,
3404            validation_attempted: ValidationAttempted::Full,
3405            is_default: false,
3406            is_nil: false,
3407            content_type: None,
3408            typed_value: None,
3409            normalized_value: if tokens.is_empty() {
3410                None
3411            } else {
3412                Some(tokens.join(" "))
3413            },
3414            schema_error_codes: Vec::new(),
3415            notation: None,
3416            deferred_by_cta: false,
3417            type_source: None,
3418            #[cfg(feature = "xsd11")]
3419            cta_selected: false,
3420            #[cfg(feature = "xsd11")]
3421            assertion_outcome: None,
3422        }
3423    }
3424
3425    /// Validate `xsi:noNamespaceSchemaLocation` as `xs:anyURI`.
3426    fn validate_xsi_no_ns_schema_location(&mut self, value: &str) -> SchemaInfo {
3427        let builtin = self.schema_set.builtin_types();
3428        let any_uri_key = TypeKey::Simple(builtin.any_uri);
3429        let attr_key = builtin.xsi_no_namespace_schema_location_attr;
3430        let mut result = self.validate_xsi_simple_value(value, any_uri_key, attr_key);
3431
3432        let trimmed = value.trim();
3433        if !trimmed.is_empty() {
3434            // XSD 1.0 §4.3.2 Rule 4 (late-arriving components): only fire the
3435            // rule when the announced location is *new* — re-announcing the
3436            // same noNamespace schema document already seen on an earlier
3437            // element does not introduce new components and is allowed.
3438            // Gated to XSD 1.0; XSD 1.1 §G.1.15 relaxes the rule.
3439            if self.schema_set.is_xsd10() {
3440                let current_serial = self.validation_stack.last().map(|ev| ev.element_serial);
3441                if let Some(serial) = current_serial {
3442                    if let Some(&first_serial) = self.first_namespace_use.get(&None) {
3443                        if first_serial != serial
3444                            && !self
3445                                .no_namespace_schema_location_hints
3446                                .iter()
3447                                .any(|h| h.location == trimmed)
3448                        {
3449                            self.report_error(
3450                                "sch-late-component",
3451                                format!(
3452                                    "xsi:noNamespaceSchemaLocation announces a new schema \
3453                                     document '{trimmed}' for the absent namespace, but \
3454                                     an item from the absent namespace has already been \
3455                                     encountered earlier in the instance document (XSD 1.0 \
3456                                     §4.3.2 Rule 4 'late-arriving components')"
3457                                ),
3458                            );
3459                            result.validity = SchemaValidity::Invalid;
3460                        }
3461                    }
3462                }
3463            }
3464
3465            self.no_namespace_schema_location_hints
3466                .push(NoNamespaceSchemaLocationHint {
3467                    location: trimmed.to_string(),
3468                    base_uri: self.current_element_base_uri(),
3469                });
3470        }
3471
3472        result
3473    }
3474
3475    // -----------------------------------------------------------------------
3476    // Identity constraint helpers
3477    // -----------------------------------------------------------------------
3478
3479    /// Resolve a keyref's `refer` target to a concrete `IdentityConstraintKey`.
3480    ///
3481    /// Scans the identity constraint arena for a Key or Unique constraint
3482    /// whose name and target namespace match the given values.
3483    fn resolve_refer_key(
3484        &self,
3485        refer_local_name: NameId,
3486        refer_ns: Option<NameId>,
3487    ) -> Option<IdentityConstraintKey> {
3488        for (key, ic_data) in &self.schema_set.arenas.identity_constraints {
3489            if ic_data.kind == IdentityKind::Keyref {
3490                continue;
3491            }
3492            if ic_data.name != refer_local_name {
3493                continue;
3494            }
3495            let ic_target_ns = ic_data
3496                .source
3497                .as_ref()
3498                .and_then(|s| self.schema_set.documents.get(s.doc_id as usize))
3499                .and_then(|d| d.target_namespace);
3500            if ic_target_ns == refer_ns {
3501                return Some(key);
3502            }
3503        }
3504        None
3505    }
3506
3507    /// Lazily compile an identity constraint and cache it.
3508    /// Returns `true` if compilation succeeded (constraint is usable).
3509    fn ensure_compiled(&mut self, ic_key: IdentityConstraintKey) -> bool {
3510        if let Some(cached) = self.compiled_constraints.get(&ic_key) {
3511            return cached.is_some();
3512        }
3513        let ic_data = &self.schema_set.arenas.identity_constraints[ic_key];
3514        let doc = ic_data
3515            .source
3516            .as_ref()
3517            .and_then(|s| self.schema_set.documents.get(s.doc_id as usize));
3518        let schema_xpath_default_ns = doc.and_then(|d| d.xpath_default_namespace);
3519        let target_namespace = doc.and_then(|d| d.target_namespace);
3520        let ic_name = ic_data.name;
3521        match CompiledIdentityConstraint::compile(
3522            ic_data,
3523            ic_key,
3524            &self.schema_set.name_table,
3525            schema_xpath_default_ns,
3526            target_namespace,
3527            self.schema_set.xsd_version,
3528        ) {
3529            Ok(mut compiled) => {
3530                // Resolve refer_key for keyref constraints
3531                if compiled.kind == IdentityKind::Keyref {
3532                    if let Some(refer) = &compiled.refer {
3533                        let refer_ns = refer.namespace.or(compiled.target_namespace);
3534                        compiled.refer_key = self.resolve_refer_key(refer.local_name, refer_ns);
3535                        if compiled.refer_key.is_none() {
3536                            let name = self.schema_set.name_table.resolve(ic_name);
3537                            let refer_display = format_resolved_qname(
3538                                &self.schema_set.name_table,
3539                                refer_ns,
3540                                refer.local_name,
3541                            );
3542                            self.sink.on_warning(ValidationWarning {
3543                                code: "cvc-identity-constraint",
3544                                message: format!(
3545                                    "Keyref '{}': could not resolve refer target '{}'",
3546                                    name, refer_display
3547                                ),
3548                                location: None,
3549                            });
3550                        }
3551                    }
3552                }
3553                self.compiled_constraints.insert(ic_key, Some(compiled));
3554                true
3555            }
3556            Err(e) => {
3557                let name = self.schema_set.name_table.resolve(ic_name);
3558                self.sink.on_warning(ValidationWarning {
3559                    code: "cvc-identity-constraint",
3560                    message: format!(
3561                        "Identity constraint '{}': XPath compilation failed: {}",
3562                        name, e
3563                    ),
3564                    location: None,
3565                });
3566                self.compiled_constraints.insert(ic_key, None);
3567                false
3568            }
3569        }
3570    }
3571
3572    /// Advance existing constraints for a start element, then activate new
3573    /// constraints from the element declaration (if any).
3574    fn advance_constraints_start_element(
3575        &mut self,
3576        local_name: NameId,
3577        namespace: Option<NameId>,
3578        element_key: Option<ElementKey>,
3579    ) {
3580        self.advance_constraints_start_element_inner(local_name, namespace, element_key, false);
3581    }
3582
3583    /// Variant of [`advance_constraints_start_element`] used when the element
3584    /// is inside a wildcard `processContents="skip"` subtree. Bumps depth
3585    /// tracking on existing IC selectors / fields without admitting new
3586    /// matches, and never activates element-attached identity constraints:
3587    /// per XSD 1.1 §3.11.4 (and the W3C wild101..103 fixtures), skipped
3588    /// content is outside the schema's validation scope.
3589    fn advance_constraints_start_element_skipped(
3590        &mut self,
3591        local_name: NameId,
3592        namespace: Option<NameId>,
3593    ) {
3594        self.advance_constraints_start_element_inner(local_name, namespace, None, true);
3595    }
3596
3597    fn advance_constraints_start_element_inner(
3598        &mut self,
3599        local_name: NameId,
3600        namespace: Option<NameId>,
3601        element_key: Option<ElementKey>,
3602        skipped: bool,
3603    ) {
3604        let ns = namespace.unwrap_or(NameId(0));
3605
3606        // 1. Advance existing active constraints
3607        for cs in &mut self.active_constraints {
3608            if skipped {
3609                cs.start_element_skipped(local_name, ns);
3610            } else {
3611                cs.start_element(local_name, ns);
3612            }
3613        }
3614
3615        // 2. Activate new constraints from element declaration (only when
3616        //    NOT in skip mode — skipped elements have no element_key).
3617        if !skipped {
3618            if let Some(ek) = element_key {
3619                let ic_keys: Vec<IdentityConstraintKey> = self.schema_set.arenas.elements[ek]
3620                    .identity_constraints
3621                    .clone();
3622                for ic_key in ic_keys {
3623                    if self.ensure_compiled(ic_key) {
3624                        let compiled = self.compiled_constraints[&ic_key].as_ref().unwrap();
3625                        let mut cs = ConstraintStruct::new(compiled);
3626                        cs.activate();
3627                        self.active_constraints.push(cs);
3628                    }
3629                }
3630            }
3631        }
3632    }
3633
3634    /// Process identity constraints at element end: advance fields/selectors,
3635    /// deactivate finished constraints, and perform scope-local keyref
3636    /// cross-reference.
3637    fn process_constraints_end_element(
3638        &mut self,
3639        text_content: &str,
3640        typed_value: Option<&XmlValue>,
3641        is_nil: bool,
3642        is_complex_content: bool,
3643        error_codes: &mut Vec<&'static str>,
3644    ) {
3645        let name_table = &self.schema_set.name_table;
3646        let element_path = self.element_path.clone();
3647        let location = self.current_location.clone();
3648
3649        // 1. Advance all constraints (field value collection + key sequence finalization)
3650        let mut ic_errors = Vec::new();
3651        for cs in &mut self.active_constraints {
3652            let errs = cs.end_element_with_text(
3653                text_content,
3654                typed_value,
3655                is_nil,
3656                is_complex_content,
3657                name_table,
3658                &element_path,
3659                location.clone(),
3660            );
3661            ic_errors.extend(errs);
3662        }
3663        for err in ic_errors {
3664            self.emit_error_to(err, error_codes);
3665        }
3666
3667        // 2. Collect deactivated constraints (constraints whose scope element just closed)
3668        let mut deactivated: Vec<ConstraintStruct> = Vec::new();
3669        let mut i = 0;
3670        while i < self.active_constraints.len() {
3671            if !self.active_constraints[i].is_active() {
3672                deactivated.push(self.active_constraints.swap_remove(i));
3673            } else {
3674                i += 1;
3675            }
3676        }
3677
3678        // 3. Scope-local keyref cross-reference using ic_scope_tables
3679        if !deactivated.is_empty() {
3680            let mut scope_keyrefs: Vec<(KeyTable, Option<IdentityConstraintKey>)> = Vec::new();
3681
3682            for cs in deactivated {
3683                if cs.key_table.kind == IdentityKind::Keyref {
3684                    // Extract the resolved refer_key from the compiled constraint
3685                    let refer_key = self
3686                        .compiled_constraints
3687                        .get(&cs.ic_key)
3688                        .and_then(|opt| opt.as_ref())
3689                        .and_then(|compiled| compiled.refer_key);
3690                    scope_keyrefs.push((cs.key_table, refer_key));
3691                } else {
3692                    // Insert key/unique table into current scope
3693                    let scope_slot = self.ic_scope_tables.last_mut();
3694                    if let Some(slot) = scope_slot {
3695                        let scope_map = slot.get_or_insert_with(HashMap::new);
3696                        let ic_key = cs.key_table.ic_key;
3697                        scope_map
3698                            .entry(ic_key)
3699                            .and_modify(|existing| {
3700                                existing.sequences.extend(cs.key_table.sequences.clone())
3701                            })
3702                            .or_insert(cs.key_table);
3703                    }
3704                }
3705            }
3706
3707            // Cross-reference each keyref against key/unique tables in current scope.
3708            // The scope map already contains child-propagated tables.
3709            // If the target isn't in scope yet (ancestor key), defer to parent.
3710            let name_table = &self.schema_set.name_table;
3711            for (keyref_table, refer_key) in scope_keyrefs {
3712                let target = refer_key.and_then(|rk| {
3713                    self.ic_scope_tables
3714                        .last()
3715                        .and_then(|slot| slot.as_ref())
3716                        .and_then(|map| map.get(&rk))
3717                });
3718                match target {
3719                    Some(target_table) => {
3720                        let errs = keyref_table.check_keyref_against(target_table, name_table);
3721                        for err in errs {
3722                            self.emit_error_to(err, error_codes);
3723                        }
3724                    }
3725                    None => {
3726                        // Target not yet in scope — defer to ancestor element end
3727                        self.deferred_keyrefs.push((keyref_table, refer_key));
3728                    }
3729                }
3730            }
3731        }
3732    }
3733
3734    /// Check if a type (possibly a union) transitively contains xs:ID or xs:IDREF
3735    /// as a member type. Used for list(union(ID, ...)) detection.
3736    fn union_has_id_idref(&self, type_key: TypeKey) -> bool {
3737        let sk = match type_key {
3738            TypeKey::Simple(sk) => sk,
3739            _ => return false,
3740        };
3741        // Direct check: is this type itself ID or IDREF?
3742        if let Some(code) = self.schema_set.get_type_code(sk) {
3743            if matches!(code, XmlTypeCode::Id | XmlTypeCode::IdRef) {
3744                return true;
3745            }
3746        }
3747        // Check union members
3748        if let Some(st) = self.schema_set.arenas.simple_types.get(sk) {
3749            for &member_key in &st.resolved_member_types {
3750                if self.union_has_id_idref(member_key) {
3751                    return true;
3752                }
3753            }
3754        }
3755        false
3756    }
3757
3758    /// Check if an ENTITY/ENTITIES value names declared unparsed entities.
3759    ///
3760    /// §3.16.4 String Valid clause 3: "Every ENTITY value in V is a declared
3761    /// entity name." Only checked when `unparsed_entities` is set.
3762    fn check_entity_declared(&mut self, typed_value: &XmlValue) {
3763        use crate::types::value::XmlValueKind;
3764        let entities = match &self.unparsed_entities {
3765            Some(e) => e,
3766            None => return,
3767        };
3768        // Collect undeclared names first to avoid borrow conflict with report_error
3769        let mut undeclared: Vec<String> = Vec::new();
3770
3771        // Check list values first — handles both built-in xs:ENTITIES and
3772        // custom <xs:list itemType="xs:ENTITY"> which has type_code=Entity
3773        // but XmlValueKind::List.
3774        if let XmlValueKind::List { item_type, items } = &typed_value.value {
3775            if *item_type == XmlTypeCode::Entity || typed_value.type_code == XmlTypeCode::Entities {
3776                for item in items {
3777                    let name = item.to_string();
3778                    if !entities.contains(&name) {
3779                        undeclared.push(name);
3780                    }
3781                }
3782            }
3783        } else if typed_value.type_code == XmlTypeCode::Entity {
3784            let name = typed_value.to_string_value();
3785            if !entities.contains(&name) {
3786                undeclared.push(name);
3787            }
3788        }
3789
3790        for name in undeclared {
3791            self.report_error(
3792                "cvc-datatype-valid.1.2.1",
3793                format!("ENTITY '{}' is not declared as an unparsed entity", name),
3794            );
3795        }
3796    }
3797
3798    /// Register an ID value with its owner element serial.
3799    ///
3800    /// XSD 1.1 §3.17.5.2: the [binding] is a set of elements. Same ID on the
3801    /// same owner element is allowed (set size stays 1); same ID on different
3802    /// elements is cvc-id.2.
3803    fn register_id_value(&mut self, value: String, owner_serial: u64) {
3804        match self.id_values.get(&value) {
3805            Some(&existing_serial) => {
3806                if !(self.schema_set.is_xsd11() && existing_serial == owner_serial) {
3807                    self.report_error("cvc-id.2", format!("Duplicate ID value '{}'", value));
3808                }
3809            }
3810            None => {
3811                self.id_values.insert(value, owner_serial);
3812            }
3813        }
3814    }
3815
3816    /// Detect ID/IDREF types and collect values for finalization.
3817    ///
3818    /// Uses the normalized value from `typed_value` (not raw `value_str`)
3819    /// for ID and IDREF tracking, so whitespace-collapsed values match
3820    /// consistently across ID, IDREF, and IDREFS.
3821    ///
3822    /// `owner_serial` identifies the element that owns the ID binding per
3823    /// §3.17.5.2: for attributes it is the element carrying the attribute;
3824    /// for element text content it is the parent element.
3825    ///
3826    /// Handles both built-in types and user-defined list/union types
3827    /// containing ID/IDREF (§3.17.5.2: eligible items include types
3828    /// "derived or constructed directly or indirectly from" ID/IDREF).
3829    fn collect_id_idref(&mut self, typed_value: &XmlValue, value_str: &str, owner_serial: u64) {
3830        use crate::types::value::XmlValueKind;
3831
3832        // Unwrap union recursively to get the effective value
3833        let mut effective = typed_value;
3834        while let XmlValueKind::Union(inner) = &effective.value {
3835            effective = inner.as_ref();
3836        }
3837
3838        // Check list values — handles custom list-of-ID, list-of-IDREF,
3839        // and list-of-union(ID/IDREF, ...) types.
3840        if let XmlValueKind::List { item_type, items } = &effective.value {
3841            // First check: is the list's item type a union containing ID/IDREF?
3842            // If so, per-item type codes vary; re-validate each token individually
3843            // to identify which items are ID vs IDREF vs other (§3.17.5.2).
3844            let union_resolved = effective
3845                .schema_type
3846                .and_then(|sk| self.schema_set.arenas.simple_types.get(sk))
3847                .and_then(|st| st.resolved_item_type)
3848                .filter(|item_tk| self.union_has_id_idref(*item_tk));
3849            if let Some(item_type_key) = union_resolved {
3850                for token in value_str.split_whitespace() {
3851                    if let Ok(result) =
3852                        super::simple::validate_simple_type(token, item_type_key, self.schema_set)
3853                    {
3854                        match result.typed_value.type_code {
3855                            XmlTypeCode::Id => {
3856                                self.register_id_value(token.to_string(), owner_serial);
3857                            }
3858                            XmlTypeCode::IdRef => {
3859                                self.pending_idrefs.push((
3860                                    token.to_string(),
3861                                    self.current_location.clone(),
3862                                    self.element_path.clone(),
3863                                ));
3864                            }
3865                            _ => {} // e.g. integer — not ID/IDREF
3866                        }
3867                    }
3868                }
3869                return;
3870            }
3871            // Simple case: all items have the same type (non-union item type)
3872            match *item_type {
3873                XmlTypeCode::Id => {
3874                    for item in items {
3875                        self.register_id_value(item.to_string(), owner_serial);
3876                    }
3877                    return;
3878                }
3879                XmlTypeCode::IdRef => {
3880                    for item in items {
3881                        self.pending_idrefs.push((
3882                            item.to_string(),
3883                            self.current_location.clone(),
3884                            self.element_path.clone(),
3885                        ));
3886                    }
3887                    return;
3888                }
3889                _ => {}
3890            }
3891        }
3892
3893        // Fall back to type_code-based dispatch for atomic values and
3894        // built-in list types (IdRefs, Entities).
3895        match effective.type_code {
3896            XmlTypeCode::Id => {
3897                let normalized = effective.to_string_value();
3898                self.register_id_value(normalized, owner_serial);
3899            }
3900            XmlTypeCode::IdRef | XmlTypeCode::IdRefs => {
3901                if let XmlValueKind::List { items, .. } = &effective.value {
3902                    for item in items {
3903                        self.pending_idrefs.push((
3904                            item.to_string(),
3905                            self.current_location.clone(),
3906                            self.element_path.clone(),
3907                        ));
3908                    }
3909                } else if effective.type_code == XmlTypeCode::IdRefs {
3910                    // Fallback for IdRefs without parsed list: split lexical text
3911                    for token in value_str.split_whitespace() {
3912                        self.pending_idrefs.push((
3913                            token.to_string(),
3914                            self.current_location.clone(),
3915                            self.element_path.clone(),
3916                        ));
3917                    }
3918                } else {
3919                    // Single IdRef value
3920                    self.pending_idrefs.push((
3921                        effective.to_string_value(),
3922                        self.current_location.clone(),
3923                        self.element_path.clone(),
3924                    ));
3925                }
3926            }
3927            _ => {}
3928        }
3929    }
3930
3931    /// Validate a single attribute against a complex type's attribute
3932    /// declarations, wildcards, and fixed-value constraints. Shared by
3933    /// `validate_attribute` and `validate_deferred_attributes`.
3934    fn validate_attribute_against_type(
3935        &mut self,
3936        ct_key: ComplexTypeKey,
3937        local_name: NameId,
3938        namespace: Option<NameId>,
3939        value: &str,
3940    ) -> SchemaInfo {
3941        let found = {
3942            let ct_data = &self.schema_set.arenas.complex_types[ct_key];
3943            self.find_attribute_in_type(ct_data, local_name, namespace)
3944        };
3945        // cvc-complex-type.3.2.2: clause 3.2.2 (wildcard) is checked
3946        // independently of 3.2.1 — a Prohibited declaration does not block a
3947        // matching wildcard (XSD 1.0 behaviour; W3C attZ002, addB034, addB136).
3948        // Applies identically under XSD 1.1: §3.2.2 mapping drops
3949        // use="prohibited" from {attribute uses}, so §3.4.4.2 clause 2.1
3950        // never matches and the fall-through to clause 2.2 (wildcard) is
3951        // the spec-compliant rescue. The §3.4.4.2 clause-4 Note about
3952        // "attribute use always takes precedence" is non-normative and
3953        // addresses only non-prohibited matches.
3954        //
3955        // The wildcard is the FULL effective attribute wildcard per
3956        // §3.6.2.2 (own + groups intersection) chained with §3.4.2.5's
3957        // extension-union over the base chain — `compute_runtime_attribute_wildcard`
3958        // returns this canonical form. Cached so the rescued-prohibited
3959        // path does not recompute when falling through to NotFound.
3960        let mut wildcard_cache: Option<
3961            Option<crate::schema::derivation::EffectiveAttributeWildcard>,
3962        > = None;
3963        let found = match found {
3964            AttributeLookup::Prohibited => {
3965                let wc = crate::schema::derivation::compute_runtime_attribute_wildcard(
3966                    self.schema_set,
3967                    ct_key,
3968                );
3969                let rescued = match wc.as_ref() {
3970                    Some(w) => crate::schema::derivation::effective_wildcard_allows_attribute(
3971                        self.schema_set,
3972                        w,
3973                        namespace,
3974                        local_name,
3975                    ),
3976                    None => false,
3977                };
3978                wildcard_cache = Some(wc);
3979                if rescued {
3980                    AttributeLookup::NotFound
3981                } else {
3982                    AttributeLookup::Prohibited
3983                }
3984            }
3985            other => other,
3986        };
3987
3988        match found {
3989            AttributeLookup::Found(attr_key, attr_type, fixed_value, inheritable) => {
3990                // Parse value once; reused for the fixed-value check and for SchemaInfo.
3991                let mut member_type = None;
3992                let mut typed_value = None;
3993                let mut normalized_value = None;
3994                let mut attr_validity = SchemaValidity::Valid;
3995                if let Some(type_key) = attr_type {
3996                    match super::simple::validate_simple_type(value, type_key, self.schema_set) {
3997                        Ok(result) => {
3998                            member_type = result.member_type;
3999                            typed_value = Some(result.typed_value);
4000                            normalized_value = result.normalized_value;
4001                        }
4002                        Err(err) => {
4003                            self.report_validation_error(err);
4004                            attr_validity = SchemaValidity::Invalid;
4005                            self.mark_current_invalid();
4006                        }
4007                    }
4008                }
4009
4010                // QName/NOTATION prefix-binding check (Datatypes §3.3.18 /
4011                // §3.3.19) — the simple-type validator can't see namespaces.
4012                if attr_validity == SchemaValidity::Valid {
4013                    match self.resolve_attribute_qname_typed_value(typed_value.as_ref(), value) {
4014                        Ok(Some(resolved)) => typed_value = Some(resolved),
4015                        Ok(None) => {}
4016                        Err(prefix) => {
4017                            let attr_name = self.schema_set.name_table.resolve(local_name);
4018                            self.report_error(
4019                                "cvc-datatype-valid.1.2.1",
4020                                format!(
4021                                    "Attribute '{}' has QName value '{}' with undeclared prefix '{}'",
4022                                    attr_name, value, prefix
4023                                ),
4024                            );
4025                            self.mark_current_invalid();
4026                            attr_validity = SchemaValidity::Invalid;
4027                        }
4028                    }
4029                }
4030
4031                if let Some(fixed) = fixed_value {
4032                    let matches = if let Some(ref tv) = typed_value {
4033                        super::simple::fixed_matches_typed(
4034                            value,
4035                            tv,
4036                            &fixed,
4037                            attr_type,
4038                            self.schema_set,
4039                        )
4040                    } else {
4041                        super::simple::fixed_values_equal(value, &fixed, attr_type, self.schema_set)
4042                    };
4043                    if !matches {
4044                        let attr_name = self.schema_set.name_table.resolve(local_name);
4045                        self.report_error(
4046                            "cvc-attribute.4",
4047                            format!(
4048                                "Attribute '{}' has fixed value '{}' but got '{}'",
4049                                attr_name, fixed, value
4050                            ),
4051                        );
4052                        self.mark_current_invalid();
4053                    }
4054                }
4055
4056                // Record inheritable attribute into outgoing map for descendants
4057                // (XSD 1.1 §3.3.5.6). This shadows any ancestor value with the
4058                // same expanded name per the nearest-owner rule.
4059                #[cfg(feature = "xsd11")]
4060                if inheritable {
4061                    if let Some(ev) = self.validation_stack.last_mut() {
4062                        use super::context::InheritedAttributeValue;
4063                        ev.outgoing_inherited.insert(
4064                            (namespace, local_name),
4065                            InheritedAttributeValue {
4066                                value: value.to_string(),
4067                                attribute_key: attr_key,
4068                            },
4069                        );
4070                    }
4071                }
4072                let _ = inheritable;
4073
4074                let result = SchemaInfo {
4075                    element_decl: None,
4076                    attribute_decl: attr_key,
4077                    schema_type: attr_type,
4078                    member_type,
4079                    validity: attr_validity,
4080                    validation_attempted: ValidationAttempted::Full,
4081                    is_default: false,
4082                    is_nil: false,
4083                    content_type: None,
4084                    typed_value,
4085                    normalized_value,
4086                    schema_error_codes: Vec::new(),
4087                    notation: None,
4088                    deferred_by_cta: false,
4089                    type_source: Some(TypeSource::Declaration),
4090                    #[cfg(feature = "xsd11")]
4091                    cta_selected: false,
4092                    #[cfg(feature = "xsd11")]
4093                    assertion_outcome: None,
4094                };
4095                self.post_process_attribute(local_name, namespace, value, &result);
4096                result
4097            }
4098            AttributeLookup::Prohibited => {
4099                let attr_name = self.schema_set.name_table.resolve(local_name);
4100                self.report_error(
4101                    "cvc-complex-type.3.2.2",
4102                    format!("Attribute '{}' is prohibited", attr_name),
4103                );
4104                self.mark_current_invalid();
4105                SchemaInfo::invalid()
4106            }
4107            AttributeLookup::NotFound => {
4108                let effective_wildcard = wildcard_cache.unwrap_or_else(|| {
4109                    crate::schema::derivation::compute_runtime_attribute_wildcard(
4110                        self.schema_set,
4111                        ct_key,
4112                    )
4113                });
4114                if let Some(ref wildcard) = effective_wildcard {
4115                    if crate::schema::derivation::effective_wildcard_allows_attribute(
4116                        self.schema_set,
4117                        wildcard,
4118                        namespace,
4119                        local_name,
4120                    ) {
4121                        let result = match wildcard.process_contents {
4122                            ProcessContents::Skip => SchemaInfo::empty(),
4123                            ProcessContents::Strict => self
4124                                .validate_wildcard_attribute_strict(local_name, namespace, value),
4125                            ProcessContents::Lax => {
4126                                self.validate_wildcard_attribute_lax(local_name, namespace, value)
4127                            }
4128                        };
4129                        // Wildcard-backed inheritance (XSD 1.1 §3.3.5.6 clause 3.2):
4130                        // If strict/lax resolved a governing declaration with
4131                        // {inheritable}=true, record for propagation. Skip has no
4132                        // governing declaration (attribute_decl is None).
4133                        #[cfg(feature = "xsd11")]
4134                        if let Some(attr_key) = result.attribute_decl {
4135                            if let Some(decl) = self.schema_set.arenas.attributes.get(attr_key) {
4136                                if decl.inheritable {
4137                                    if let Some(ev) = self.validation_stack.last_mut() {
4138                                        use super::context::InheritedAttributeValue;
4139                                        ev.outgoing_inherited.insert(
4140                                            (namespace, local_name),
4141                                            InheritedAttributeValue {
4142                                                value: value.to_string(),
4143                                                attribute_key: Some(attr_key),
4144                                            },
4145                                        );
4146                                    }
4147                                }
4148                            }
4149                        }
4150                        if wildcard.process_contents == ProcessContents::Skip {
4151                            // Skip-processed attributes cannot contribute a typed value to
4152                            // an IC field (§3.11.4), so the field slot stays absent — xs:key
4153                            // will report a missing-field violation.  However, the attribute
4154                            // IS still selected by the field XPath, so it must count toward
4155                            // multi-node detection (cvc-identity-constraint.4.2.1).
4156                            let ns = namespace.unwrap_or(NameId(0));
4157                            let mut multi_node_ic: Vec<(NameId, usize)> = Vec::new();
4158                            for cs in &mut self.active_constraints {
4159                                let matches = cs.matching_fields(local_name, ns);
4160                                for field_idx in matches {
4161                                    if cs.increment_field_match_count(field_idx) {
4162                                        multi_node_ic
4163                                            .push((cs.key_table.constraint_name, field_idx));
4164                                    }
4165                                }
4166                            }
4167                            for (constraint_name, field_idx) in multi_node_ic {
4168                                let name = self
4169                                    .schema_set
4170                                    .name_table
4171                                    .resolve(constraint_name)
4172                                    .to_string();
4173                                self.report_error(
4174                                    "cvc-identity-constraint.4.2.1",
4175                                    format!(
4176                                        "Identity constraint '{}': field {} matches more than one node",
4177                                        name, field_idx + 1
4178                                    ),
4179                                );
4180                            }
4181                        } else {
4182                            self.post_process_attribute(local_name, namespace, value, &result);
4183                        }
4184                        return result;
4185                    }
4186                }
4187
4188                let attr_name = self.schema_set.name_table.resolve(local_name);
4189                self.report_error(
4190                    "cvc-complex-type.3.2.2",
4191                    format!("Attribute '{}' is not allowed for this element", attr_name),
4192                );
4193                self.mark_current_invalid();
4194                SchemaInfo::invalid()
4195            }
4196        }
4197    }
4198
4199    /// Re-validate attributes that were deferred during CTA evaluation.
4200    ///
4201    /// Called from `validate_end_of_attributes()` after the type alternative
4202    /// has been selected.
4203    #[cfg(feature = "xsd11")]
4204    fn validate_deferred_attributes(&mut self, schema_type: Option<TypeKey>) {
4205        self.deferred_attribute_results.clear();
4206
4207        let collected = match self.validation_stack.last_mut() {
4208            Some(ev) => std::mem::take(&mut ev.collected_attributes),
4209            None => return,
4210        };
4211
4212        let ct_key = match schema_type {
4213            Some(TypeKey::Complex(k)) => k,
4214            _ => {
4215                // Non-complex type (e.g. simple type selected by CTA):
4216                // no attribute declarations exist, but we must produce one
4217                // result per collected attribute to keep the 1:1 invariant
4218                // with the typed builder's deferred_attr_refs.
4219                self.deferred_attribute_results
4220                    .resize_with(collected.len(), SchemaInfo::empty);
4221                return;
4222            }
4223        };
4224
4225        for (namespace, local_name, value) in &collected {
4226            let ec_snapshot = self
4227                .validation_stack
4228                .last()
4229                .map_or(0, |ev| ev.error_codes.len());
4230            let mut info =
4231                self.validate_attribute_against_type(ct_key, *local_name, *namespace, value);
4232            if let Some(ev) = self.validation_stack.last_mut() {
4233                if ev.error_codes.len() > ec_snapshot {
4234                    info.schema_error_codes = ev.error_codes[ec_snapshot..].to_vec();
4235                    ev.error_codes.truncate(ec_snapshot);
4236                }
4237                // Track attribute [validation attempted] on parent element
4238                match info.validation_attempted {
4239                    ValidationAttempted::Full => {
4240                        ev.any_attr_not_none = true;
4241                    }
4242                    ValidationAttempted::None => {
4243                        ev.any_attr_not_full = true;
4244                    }
4245                    ValidationAttempted::Partial => {
4246                        ev.any_attr_not_full = true;
4247                        ev.any_attr_not_none = true;
4248                    }
4249                }
4250            }
4251            self.deferred_attribute_results.push(info);
4252        }
4253    }
4254
4255    /// Drain deferred attribute validation results collected during CTA processing.
4256    ///
4257    /// Returns the `SchemaInfo` results in the same order as the attributes were
4258    /// originally encountered. The internal buffer is emptied.
4259    #[cfg(feature = "xsd11")]
4260    pub fn take_deferred_attribute_results(&mut self) -> Vec<SchemaInfo> {
4261        std::mem::take(&mut self.deferred_attribute_results)
4262    }
4263
4264    /// Post-process a validated attribute for identity constraint field matching
4265    /// and ID/IDREF collection.
4266    fn post_process_attribute(
4267        &mut self,
4268        local_name: NameId,
4269        namespace: Option<NameId>,
4270        value: &str,
4271        result: &SchemaInfo,
4272    ) {
4273        let ns = namespace.unwrap_or(NameId(0));
4274
4275        // Identity constraint: check field attribute matches
4276        let ns_ctx = self
4277            .validation_stack
4278            .last()
4279            .and_then(|ev| ev.ns_context.as_ref());
4280        let ic_typed_value = Self::resolve_ic_qname_value(
4281            &result.typed_value,
4282            value,
4283            ns_ctx,
4284            &self.schema_set.name_table,
4285        )
4286        .or_else(|| result.typed_value.clone());
4287        let mut multi_node_ic: Vec<(NameId, usize)> = Vec::new();
4288        for cs in &mut self.active_constraints {
4289            let matches = cs.matching_fields(local_name, ns);
4290            for field_idx in matches {
4291                let already_matched =
4292                    cs.set_field_value(field_idx, value.to_string(), ic_typed_value.clone());
4293                if already_matched {
4294                    multi_node_ic.push((cs.key_table.constraint_name, field_idx));
4295                }
4296            }
4297        }
4298        for (constraint_name, field_idx) in multi_node_ic {
4299            let name = self
4300                .schema_set
4301                .name_table
4302                .resolve(constraint_name)
4303                .to_string();
4304            self.report_error(
4305                "cvc-identity-constraint.4.2.1",
4306                format!(
4307                    "Identity constraint '{}': field {} matches more than one node",
4308                    name,
4309                    field_idx + 1
4310                ),
4311            );
4312        }
4313
4314        // ID/IDREF/ENTITY collection — owner is current element (attribute binding)
4315        if let Some(ref tv) = result.typed_value {
4316            let owner = self
4317                .validation_stack
4318                .last()
4319                .map(|e| e.element_serial)
4320                .unwrap_or(0);
4321            self.collect_id_idref(tv, value, owner);
4322            self.check_entity_declared(tv);
4323
4324            // NOTATION tracking (§3.14.5): set [notation] on parent element
4325            if tv.type_code == XmlTypeCode::Notation {
4326                // Resolve before borrowing the stack to avoid borrow conflict
4327                let notation = self
4328                    .validation_stack
4329                    .last()
4330                    .filter(|ev| ev.notation.is_none())
4331                    .and_then(|ev| ev.ns_context.as_ref())
4332                    .and_then(|ctx| self.resolve_notation_qname(value, ctx));
4333                if let (Some(nk), Some(ev)) = (notation, self.validation_stack.last_mut()) {
4334                    if ev.notation.is_none() {
4335                        ev.notation = Some(nk);
4336                    }
4337                }
4338            }
4339        }
4340    }
4341
4342    /// Resolve a NOTATION QName value to a NotationKey using the element's
4343    /// namespace context. Returns `None` if the QName is malformed or the
4344    /// notation is not declared.
4345    fn resolve_notation_qname(
4346        &self,
4347        value: &str,
4348        ns_context: &NamespaceContextSnapshot,
4349    ) -> Option<NotationKey> {
4350        let qn =
4351            parse_qname_with_snapshot(value, ns_context, &self.schema_set.name_table, true).ok()?;
4352        self.schema_set
4353            .lookup_notation(qn.namespace_uri, qn.local_name)
4354    }
4355
4356    /// Resolve a QName/NOTATION attribute's typed value against the current
4357    /// element's namespace context.
4358    ///
4359    /// Returns:
4360    /// - `Ok(Some(value))` — typed value has been re-built with a resolved
4361    ///   `XmlAtomicValue::QName` / `Notation`; caller should replace.
4362    /// - `Ok(None)` — nothing to do (non-QName type, no ns_context, or
4363    ///   non-`UndefinedPrefix` lexical error already flagged by the simple
4364    ///   type validator).
4365    /// - `Err(prefix)` — the attribute carries a prefix that is not in
4366    ///   scope; caller should report cvc-datatype-valid.1.2.1.
4367    fn resolve_attribute_qname_typed_value(
4368        &self,
4369        typed_value: Option<&XmlValue>,
4370        value: &str,
4371    ) -> Result<Option<XmlValue>, String> {
4372        use crate::types::value::{XmlAtomicValue, XmlValueKind};
4373        let Some(tv) = typed_value else {
4374            return Ok(None);
4375        };
4376        if tv.type_code != XmlTypeCode::QName && tv.type_code != XmlTypeCode::Notation {
4377            return Ok(None);
4378        }
4379        let Some(ctx) = self
4380            .validation_stack
4381            .last()
4382            .and_then(|ev| ev.ns_context.as_ref())
4383        else {
4384            return Ok(None);
4385        };
4386        let qn = match parse_qname_with_snapshot(value, ctx, &self.schema_set.name_table, true) {
4387            Ok(qn) => qn,
4388            Err(QNameError::UndefinedPrefix(prefix)) => return Err(prefix),
4389            Err(_) => return Ok(None),
4390        };
4391        let atom = if tv.type_code == XmlTypeCode::Notation {
4392            XmlAtomicValue::Notation(qn)
4393        } else {
4394            XmlAtomicValue::QName(qn)
4395        };
4396        Ok(Some(XmlValue {
4397            type_code: tv.type_code,
4398            schema_type: tv.schema_type,
4399            value: XmlValueKind::Atomic(atom),
4400        }))
4401    }
4402
4403    /// Resolve a QName/NOTATION-typed value for IC purposes.
4404    ///
4405    /// QName values from simple type validation are stored as `XmlAtomicValue::String`
4406    /// because namespace context wasn't available at validation time. For IC field
4407    /// value comparison, we need namespace-aware QNames. Returns a new resolved
4408    /// copy (does NOT mutate the original, preserving PSVI integrity).
4409    fn resolve_ic_qname_value(
4410        typed_value: &Option<XmlValue>,
4411        string_value: &str,
4412        ns_context: Option<&NamespaceContextSnapshot>,
4413        name_table: &crate::namespace::table::NameTable,
4414    ) -> Option<XmlValue> {
4415        use crate::types::value::{XmlAtomicValue, XmlValueKind};
4416        let val = typed_value.as_ref()?;
4417        if val.type_code != XmlTypeCode::QName && val.type_code != XmlTypeCode::Notation {
4418            return None;
4419        }
4420        // Attribute validation may have already resolved the QName/NOTATION
4421        // against the element's namespace context (see
4422        // `validate_attribute_against_type`). Skip the re-parse in that case.
4423        if matches!(
4424            val.value,
4425            XmlValueKind::Atomic(XmlAtomicValue::QName(_))
4426                | XmlValueKind::Atomic(XmlAtomicValue::Notation(_))
4427        ) {
4428            return None;
4429        }
4430        let ctx = ns_context?;
4431        let qn = parse_qname_with_snapshot(string_value, ctx, name_table, true).ok()?;
4432        let atom = if val.type_code == XmlTypeCode::Notation {
4433            XmlAtomicValue::Notation(qn)
4434        } else {
4435            XmlAtomicValue::QName(qn)
4436        };
4437        Some(XmlValue {
4438            type_code: val.type_code,
4439            schema_type: val.schema_type,
4440            value: XmlValueKind::Atomic(atom),
4441        })
4442    }
4443
4444    /// Initialize content model and ContentType from a TypeKey
4445    fn init_content_model(
4446        &self,
4447        type_key: Option<TypeKey>,
4448    ) -> (ContentValidatorState, ContentType) {
4449        match type_key {
4450            Some(TypeKey::Complex(ct_key)) => {
4451                let ct_data = &self.schema_set.arenas.complex_types[ct_key];
4452                let content_type = self.determine_content_type(ct_data);
4453
4454                let content_state = match content_type {
4455                    ContentType::Empty => ContentValidatorState::Empty,
4456                    ContentType::TextOnly => ContentValidatorState::Simple,
4457                    ContentType::ElementOnly | ContentType::Mixed => {
4458                        match compile_content_model_matcher(self.schema_set, ct_data) {
4459                            Ok(matcher) => ContentValidatorState::from_matcher(matcher),
4460                            Err(_) => {
4461                                // Compilation error — treat as empty
4462                                ContentValidatorState::Empty
4463                            }
4464                        }
4465                    }
4466                };
4467
4468                (content_state, content_type)
4469            }
4470            Some(TypeKey::Simple(_)) => (ContentValidatorState::Simple, ContentType::TextOnly),
4471            None => (ContentValidatorState::Simple, ContentType::TextOnly),
4472        }
4473    }
4474
4475    /// Determine the ContentType from a ComplexTypeDefData
4476    fn determine_content_type(&self, ct_data: &ComplexTypeDefData) -> ContentType {
4477        use crate::parser::frames::ComplexContentResult;
4478        use crate::parser::frames::DerivationMethod;
4479        match &ct_data.content {
4480            ComplexContentResult::Empty => {
4481                // §3.4.2.3 step 5.2: a `<defaultOpenContent>` with
4482                // `appliesToEmpty="true"` widens an otherwise-empty
4483                // content type to element-only/mixed (XSD 1.1).
4484                #[cfg(feature = "xsd11")]
4485                if self.schema_set.is_xsd11()
4486                    && self.empty_ct_has_applicable_default_open_content(ct_data)
4487                {
4488                    return if ct_data.mixed {
4489                        ContentType::Mixed
4490                    } else {
4491                        ContentType::ElementOnly
4492                    };
4493                }
4494                if ct_data.mixed {
4495                    ContentType::Mixed
4496                } else {
4497                    ContentType::Empty
4498                }
4499            }
4500            ComplexContentResult::Simple(_) => ContentType::TextOnly,
4501            ComplexContentResult::Complex(def) => {
4502                // §3.4.2.3 step 4.1.1 / 4.2.1: explicit content type variety
4503                // = empty when the effective content is empty (the explicit
4504                // content is empty *and* effective mixed is false). Step 6
4505                // then promotes variety to element-only/mixed if a wildcard
4506                // element (open content) is present — without one, the final
4507                // {content type} stays empty (no character or element items
4508                // are allowed). Only apply the relaxed §3.4.2.3 step 2.1.x
4509                // particle-shape recognition under XSD 1.1; XSD 1.0 tests
4510                // historically rely on `def.particle.is_none()` strictness.
4511                let particle_empty = match &def.particle {
4512                    None => true,
4513                    Some(p) => {
4514                        self.schema_set.is_xsd11()
4515                            && crate::parser::frames::particle_is_explicit_empty(p)
4516                    }
4517                };
4518                let has_open_content = self.type_has_effective_open_content(ct_data, def);
4519                if particle_empty && !ct_data.mixed && !def.mixed && !has_open_content {
4520                    // For extensions with no own particle, inherit base type's content type
4521                    if matches!(ct_data.derivation_method, Some(DerivationMethod::Extension)) {
4522                        if let Some(TypeKey::Complex(base_ct_key)) = ct_data.resolved_base_type {
4523                            let base_data = &self.schema_set.arenas.complex_types[base_ct_key];
4524                            return self.determine_content_type(base_data);
4525                        }
4526                    }
4527                    ContentType::Empty
4528                } else if ct_data.mixed || def.mixed {
4529                    ContentType::Mixed
4530                } else {
4531                    ContentType::ElementOnly
4532                }
4533            }
4534        }
4535    }
4536
4537    /// Whether a complex type effectively has open content per §3.4.2.3
4538    /// step 5 (the "wildcard element"). Used by `determine_content_type` to
4539    /// distinguish a truly empty content type from one whose explicit content
4540    /// type variety = empty but is widened to element-only by an open content
4541    /// wildcard (own `<openContent>` or applicable `<defaultOpenContent>`).
4542    /// Whether an otherwise-empty (`ComplexContentResult::Empty`) complex
4543    /// type effectively gains a wildcard via the schema document's
4544    /// `<defaultOpenContent>` with `appliesToEmpty="true"`. Mirrors the
4545    /// `appliesToEmpty` half of `type_has_effective_open_content` for the
4546    /// shorter syntax CTs that don't carry a `ComplexContentDefResult`.
4547    #[cfg(feature = "xsd11")]
4548    fn empty_ct_has_applicable_default_open_content(&self, ct_data: &ComplexTypeDefData) -> bool {
4549        let doc = ct_data
4550            .source
4551            .as_ref()
4552            .and_then(|s| self.schema_set.documents.get(s.defaults_doc() as usize));
4553        if let Some(default) = doc.and_then(|d| d.default_open_content.as_ref()) {
4554            if default.mode == crate::schema::model::OpenContentMode::None {
4555                return false;
4556            }
4557            return default.applies_to_empty;
4558        }
4559        false
4560    }
4561
4562    #[allow(unused_variables)]
4563    fn type_has_effective_open_content(
4564        &self,
4565        ct_data: &ComplexTypeDefData,
4566        def: &crate::parser::frames::ComplexContentDefResult,
4567    ) -> bool {
4568        #[cfg(feature = "xsd11")]
4569        {
4570            use crate::parser::frames::OpenContentMode;
4571            // Step 5.1: own <openContent> child with mode != none.
4572            if let Some(oc) = def.open_content.as_ref() {
4573                return oc.mode != OpenContentMode::None;
4574            }
4575            // Step 5.2: schema's <defaultOpenContent>. Applies when
4576            // appliesToEmpty=true OR explicit content type variety ≠ empty.
4577            // Use the same gate as compile.rs `resolve_open_content`.
4578            let doc = ct_data
4579                .source
4580                .as_ref()
4581                .and_then(|s| self.schema_set.documents.get(s.defaults_doc() as usize));
4582            if let Some(default) = doc.and_then(|d| d.default_open_content.as_ref()) {
4583                if default.mode == crate::schema::model::OpenContentMode::None {
4584                    return false;
4585                }
4586                if default.applies_to_empty {
4587                    return true;
4588                }
4589                return !ct_data.content.explicit_content_type_is_empty();
4590            }
4591        }
4592        let _ = (ct_data, def);
4593        false
4594    }
4595
4596    /// Resolve an xsi:type QName string to an [`XsiTypeOutcome`].
4597    ///
4598    /// Errors are collected in `deferred_errors` instead of being emitted
4599    /// immediately, so the caller can emit them after the child element is
4600    /// pushed onto the validation stack (ensuring correct PSVI attribution).
4601    fn resolve_xsi_type(
4602        &self,
4603        xsi_type_str: &str,
4604        declared_type: Option<TypeKey>,
4605        block: DerivationSet,
4606        ns_context: &NamespaceContextSnapshot,
4607        deferred_errors: &mut Vec<(&'static str, String)>,
4608    ) -> XsiTypeOutcome {
4609        // Parse and validate the QName using shared parsing logic
4610        let qname = match parse_qname_with_snapshot(
4611            xsi_type_str,
4612            ns_context,
4613            &self.schema_set.name_table,
4614            true,
4615        ) {
4616            Ok(qn) => qn,
4617            Err(e) => {
4618                let msg = match e {
4619                    QNameError::UndefinedPrefix(p) => {
4620                        format!(
4621                            "Undeclared prefix '{}' in xsi:type value '{}'",
4622                            p, xsi_type_str
4623                        )
4624                    }
4625                    _ => format!("Invalid xsi:type value '{}': {}", xsi_type_str, e),
4626                };
4627                deferred_errors.push(("cvc-elt.4.1", msg));
4628                return XsiTypeOutcome::Unresolved;
4629            }
4630        };
4631
4632        // Look up the type
4633        let resolved = self
4634            .schema_set
4635            .lookup_type(qname.namespace_uri, qname.local_name)
4636            .or_else(|| {
4637                self.schema_set
4638                    .get_built_in_type_by_qname(qname.namespace_uri, qname.local_name)
4639            });
4640
4641        match resolved {
4642            Some(type_key) => {
4643                // Validate derivation: the xsi:type must derive from the declared type
4644                if let Some(declared) = declared_type {
4645                    // cvc-elt.4.2: basic derivation check (no block keywords)
4646                    if !self.schema_set.is_type_derived_from(
4647                        type_key,
4648                        declared,
4649                        DerivationSet::empty(),
4650                    ) {
4651                        deferred_errors.push((
4652                            "cvc-elt.4.2",
4653                            format!(
4654                                "xsi:type '{}' does not derive from the declared type",
4655                                xsi_type_str
4656                            ),
4657                        ));
4658                        return XsiTypeOutcome::InvalidDerivation;
4659                    }
4660                    // cvc-elt.4.3: validly substitutable — combine the element's
4661                    // {disallowed substitutions} (block) with the declared type's
4662                    // {prohibited substitutions} into a single exclusion mask.
4663                    let mut combined_block = block;
4664                    if let TypeKey::Complex(declared_ct_key) = declared {
4665                        if let Some(declared_ct) =
4666                            self.schema_set.arenas.complex_types.get(declared_ct_key)
4667                        {
4668                            combined_block |= declared_ct.block.element_block_mask();
4669                        }
4670                    }
4671                    if !combined_block.is_empty()
4672                        && !self
4673                            .schema_set
4674                            .is_type_derived_from(type_key, declared, combined_block)
4675                    {
4676                        deferred_errors.push((
4677                            "cvc-elt.4.3",
4678                            format!(
4679                                "xsi:type '{}' is not validly substitutable for the declared type \
4680                                 (blocked by 'block' attribute)",
4681                                xsi_type_str
4682                            ),
4683                        ));
4684                        return XsiTypeOutcome::InvalidDerivation;
4685                    }
4686                }
4687                XsiTypeOutcome::Applied(type_key)
4688            }
4689            None => {
4690                deferred_errors.push((
4691                    "cvc-elt.4.1",
4692                    format!(
4693                        "Type '{}' specified in xsi:type is not declared",
4694                        xsi_type_str
4695                    ),
4696                ));
4697                XsiTypeOutcome::Unresolved
4698            }
4699        }
4700    }
4701
4702    /// Emit deferred xsi:type errors (collected by [`resolve_xsi_type`]).
4703    fn emit_deferred_xsi_type_errors(&mut self, errors: Vec<(&'static str, String)>) {
4704        for (constraint, message) in errors {
4705            self.report_error(constraint, message);
4706        }
4707    }
4708
4709    /// Returns xs:anyType's content model for lax assessment.
4710    ///
4711    /// The caller keeps `schema_type = None` (no governing type) — only the
4712    /// content model (Mixed + `(xs:any processContents=lax)*`) is used.
4713    fn lax_assessment_content_model(&self) -> (ContentValidatorState, ContentType) {
4714        let any_type_key = TypeKey::Complex(self.schema_set.any_type_key());
4715        self.init_content_model(Some(any_type_key))
4716    }
4717
4718    /// Validate an attribute matched by a wildcard with processContents="strict".
4719    ///
4720    /// A global attribute declaration must exist; its value is validated against
4721    /// the declared type.
4722    fn validate_wildcard_attribute_strict(
4723        &mut self,
4724        local_name: NameId,
4725        namespace: Option<NameId>,
4726        value: &str,
4727    ) -> SchemaInfo {
4728        match self.schema_set.lookup_attribute(namespace, local_name) {
4729            Some(attr_key) => {
4730                let attr_data = self.schema_set.arenas.attributes.get(attr_key);
4731                let attr_type = attr_data.and_then(|d| d.resolved_type);
4732                let fixed = attr_data.and_then(|d| d.fixed_value.clone());
4733
4734                // Parse value once; reused for the fixed-value check and SchemaInfo.
4735                let mut member_type = None;
4736                let mut typed_value = None;
4737                let mut normalized_value = None;
4738                let mut attr_validity = SchemaValidity::Valid;
4739                if let Some(type_key) = attr_type {
4740                    match super::simple::validate_simple_type(value, type_key, self.schema_set) {
4741                        Ok(result) => {
4742                            member_type = result.member_type;
4743                            typed_value = Some(result.typed_value);
4744                            normalized_value = result.normalized_value;
4745                        }
4746                        Err(err) => {
4747                            self.report_validation_error(err);
4748                            attr_validity = SchemaValidity::Invalid;
4749                            if let Some(s) = self.validation_stack.last_mut() {
4750                                s.validity = SchemaValidity::Invalid;
4751                            }
4752                        }
4753                    }
4754                }
4755
4756                if let Some(fixed_val) = fixed {
4757                    let matches = if let Some(ref tv) = typed_value {
4758                        super::simple::fixed_matches_typed(
4759                            value,
4760                            tv,
4761                            &fixed_val,
4762                            attr_type,
4763                            self.schema_set,
4764                        )
4765                    } else {
4766                        super::simple::fixed_values_equal(
4767                            value,
4768                            &fixed_val,
4769                            attr_type,
4770                            self.schema_set,
4771                        )
4772                    };
4773                    if !matches {
4774                        let attr_name = self.schema_set.name_table.resolve(local_name);
4775                        self.report_error(
4776                            "cvc-attribute.4",
4777                            format!(
4778                                "Attribute '{}' has fixed value '{}' but got '{}'",
4779                                attr_name, fixed_val, value
4780                            ),
4781                        );
4782                        if let Some(s) = self.validation_stack.last_mut() {
4783                            s.validity = SchemaValidity::Invalid;
4784                        }
4785                    }
4786                }
4787
4788                SchemaInfo {
4789                    element_decl: None,
4790                    attribute_decl: Some(attr_key),
4791                    schema_type: attr_type,
4792                    member_type,
4793                    validity: attr_validity,
4794                    validation_attempted: ValidationAttempted::Full,
4795                    is_default: false,
4796                    is_nil: false,
4797                    content_type: None,
4798                    typed_value,
4799                    normalized_value,
4800                    schema_error_codes: Vec::new(),
4801                    notation: None,
4802                    deferred_by_cta: false,
4803                    type_source: Some(TypeSource::Declaration),
4804                    #[cfg(feature = "xsd11")]
4805                    cta_selected: false,
4806                    #[cfg(feature = "xsd11")]
4807                    assertion_outcome: None,
4808                }
4809            }
4810            None => {
4811                let attr_name = self.schema_set.name_table.resolve(local_name);
4812                self.report_error(
4813                    "cvc-assess-attr.1.2",
4814                    format!(
4815                        "No global attribute declaration for '{}' (wildcard processContents=\"strict\")",
4816                        attr_name
4817                    ),
4818                );
4819                if let Some(s) = self.validation_stack.last_mut() {
4820                    s.validity = SchemaValidity::Invalid;
4821                }
4822                SchemaInfo::invalid()
4823            }
4824        }
4825    }
4826
4827    /// Validate an attribute matched by a wildcard with processContents="lax".
4828    ///
4829    /// If a global attribute declaration exists, validate; otherwise skip.
4830    fn validate_wildcard_attribute_lax(
4831        &mut self,
4832        local_name: NameId,
4833        namespace: Option<NameId>,
4834        value: &str,
4835    ) -> SchemaInfo {
4836        match self.schema_set.lookup_attribute(namespace, local_name) {
4837            Some(attr_key) => {
4838                // Found a global declaration — validate like strict
4839                let attr_data = self.schema_set.arenas.attributes.get(attr_key);
4840                let attr_type = attr_data.and_then(|d| d.resolved_type);
4841                let fixed = attr_data.and_then(|d| d.fixed_value.clone());
4842
4843                // Parse value once; reused for the fixed-value check and SchemaInfo.
4844                let mut member_type = None;
4845                let mut typed_value = None;
4846                let mut normalized_value = None;
4847                let mut attr_validity = SchemaValidity::Valid;
4848                if let Some(type_key) = attr_type {
4849                    match super::simple::validate_simple_type(value, type_key, self.schema_set) {
4850                        Ok(result) => {
4851                            member_type = result.member_type;
4852                            typed_value = Some(result.typed_value);
4853                            normalized_value = result.normalized_value;
4854                        }
4855                        Err(err) => {
4856                            self.report_validation_error(err);
4857                            attr_validity = SchemaValidity::Invalid;
4858                            if let Some(s) = self.validation_stack.last_mut() {
4859                                s.validity = SchemaValidity::Invalid;
4860                            }
4861                        }
4862                    }
4863                }
4864
4865                if let Some(fixed_val) = fixed {
4866                    let matches = if let Some(ref tv) = typed_value {
4867                        super::simple::fixed_matches_typed(
4868                            value,
4869                            tv,
4870                            &fixed_val,
4871                            attr_type,
4872                            self.schema_set,
4873                        )
4874                    } else {
4875                        super::simple::fixed_values_equal(
4876                            value,
4877                            &fixed_val,
4878                            attr_type,
4879                            self.schema_set,
4880                        )
4881                    };
4882                    if !matches {
4883                        let attr_name = self.schema_set.name_table.resolve(local_name);
4884                        self.report_error(
4885                            "cvc-attribute.4",
4886                            format!(
4887                                "Attribute '{}' has fixed value '{}' but got '{}'",
4888                                attr_name, fixed_val, value
4889                            ),
4890                        );
4891                        if let Some(s) = self.validation_stack.last_mut() {
4892                            s.validity = SchemaValidity::Invalid;
4893                        }
4894                    }
4895                }
4896
4897                SchemaInfo {
4898                    element_decl: None,
4899                    attribute_decl: Some(attr_key),
4900                    schema_type: attr_type,
4901                    member_type,
4902                    validity: attr_validity,
4903                    validation_attempted: ValidationAttempted::Full,
4904                    is_default: false,
4905                    is_nil: false,
4906                    content_type: None,
4907                    typed_value,
4908                    normalized_value,
4909                    schema_error_codes: Vec::new(),
4910                    notation: None,
4911                    deferred_by_cta: false,
4912                    type_source: Some(TypeSource::Declaration),
4913                    #[cfg(feature = "xsd11")]
4914                    cta_selected: false,
4915                    #[cfg(feature = "xsd11")]
4916                    assertion_outcome: None,
4917                }
4918            }
4919            None => {
4920                // No global declaration — lax means skip
4921                SchemaInfo::empty()
4922            }
4923        }
4924    }
4925
4926    /// Collect all attribute uses from resolved attribute groups (recursively).
4927    fn collect_group_attributes(&self, ct_data: &ComplexTypeDefData) -> Vec<GroupAttribute> {
4928        let mut result = Vec::new();
4929        let mut visited = HashSet::new();
4930        for &group_key in &ct_data.resolved_attribute_groups {
4931            self.collect_group_attributes_recursive(group_key, &mut result, &mut visited);
4932        }
4933        result
4934    }
4935
4936    fn collect_group_attributes_recursive(
4937        &self,
4938        group_key: AttributeGroupKey,
4939        result: &mut Vec<GroupAttribute>,
4940        visited: &mut HashSet<AttributeGroupKey>,
4941    ) {
4942        if !visited.insert(group_key) {
4943            return; // prevent infinite recursion on circular refs
4944        }
4945        let group_data = match self.schema_set.arenas.get_attribute_group(group_key) {
4946            Some(g) => g,
4947            None => return,
4948        };
4949        for (i, attr_use) in group_data.attributes.iter().enumerate() {
4950            let resolved = group_data.resolved_attributes.get(i);
4951            let attr_key = resolved.and_then(|r| r.resolved_ref);
4952            // For `<xs:attribute ref="x:foo"/>` uses, the use's own
4953            // `resolved_type` is `None` — the type lives on the global
4954            // declaration. Fall back to the resolved type recorded on the
4955            // global decl, otherwise simple-type validation gets skipped
4956            // (the W3C `xsd003b` fixture exercises this through a redefined
4957            // `simpleType` whose enum-restricted facet must be applied to
4958            // an attribute reached via an attribute group).
4959            let attr_type = resolved.and_then(|r| r.resolved_type).or_else(|| {
4960                attr_key
4961                    .and_then(|k| self.schema_set.arenas.attributes.get(k))
4962                    .and_then(|d| d.resolved_type)
4963            });
4964            let (name, namespace) =
4965                self.resolve_attr_use_name_ns(attr_use, resolved, group_data.target_namespace);
4966            let fixed_value = attr_use.attribute.fixed_value.clone().or_else(|| {
4967                attr_key
4968                    .and_then(|k| self.schema_set.arenas.attributes.get(k))
4969                    .and_then(|d| d.fixed_value.clone())
4970            });
4971            let default_value = attr_use.attribute.default_value.clone().or_else(|| {
4972                attr_key
4973                    .and_then(|k| self.schema_set.arenas.attributes.get(k))
4974                    .and_then(|d| d.default_value.clone())
4975            });
4976            result.push(GroupAttribute {
4977                name,
4978                namespace,
4979                use_kind: attr_use.use_kind,
4980                type_key: attr_type,
4981                attr_key,
4982                fixed_value,
4983                default_value,
4984                #[cfg(feature = "xsd11")]
4985                inheritable: attr_use.attribute.inheritable,
4986            });
4987        }
4988        for &nested_key in &group_data.resolved_attribute_groups {
4989            self.collect_group_attributes_recursive(nested_key, result, visited);
4990        }
4991    }
4992
4993    /// Resolve the effective name and namespace for an attribute use.
4994    ///
4995    /// For inline attributes, returns the name directly from the use.
4996    /// For `<xs:attribute ref="..."/>`, resolves through the global declaration.
4997    ///
4998    /// `fallback_namespace` is used when the attribute's source document is
4999    /// unavailable (e.g. synthesized attributes); callers should pass the
5000    /// containing type's or group's target namespace.
5001    fn resolve_attr_use_name_ns(
5002        &self,
5003        attr_use: &AttributeUseResult,
5004        resolved: Option<&ResolvedAttributeUse>,
5005        fallback_namespace: Option<NameId>,
5006    ) -> (NameId, Option<NameId>) {
5007        if let Some(name) = attr_use.attribute.name {
5008            if attr_use.attribute.ref_name.is_none() {
5009                // Inline local attribute: apply form / attributeFormDefault
5010                let ns = self.schema_set.effective_local_attribute_namespace(
5011                    attr_use.attribute.target_namespace,
5012                    attr_use.attribute.form.as_deref(),
5013                    attr_use.attribute.source.as_ref(),
5014                    fallback_namespace,
5015                );
5016                return (name, ns);
5017            }
5018            // ref with name — fall through to ref resolution
5019        }
5020        if let Some(attr_key) = resolved.and_then(|r| r.resolved_ref) {
5021            if let Some(decl) = self.schema_set.arenas.attributes.get(attr_key) {
5022                if let Some(name) = decl.name {
5023                    return (name, decl.target_namespace);
5024                }
5025            }
5026        }
5027        (well_known::EMPTY, None)
5028    }
5029
5030    // Effective-attribute-wildcard helpers superseded by
5031    // `crate::schema::derivation::compute_runtime_attribute_wildcard`,
5032    // which implements full §3.6.2.2 (own + groups intersection) plus
5033    // §3.4.2.5 (extension union over the base chain) in canonical form.
5034
5035    /// Find an attribute declaration in a complex type's attribute list
5036    fn find_attribute_in_type(
5037        &self,
5038        ct_data: &ComplexTypeDefData,
5039        local_name: NameId,
5040        namespace: Option<NameId>,
5041    ) -> AttributeLookup {
5042        for (i, attr_use) in ct_data.attributes.iter().enumerate() {
5043            let resolved = ct_data.resolved_attributes.get(i);
5044            let (attr_name, attr_ns) =
5045                self.resolve_attr_use_name_ns(attr_use, resolved, ct_data.target_namespace);
5046
5047            if attr_name == local_name && attr_ns == namespace {
5048                let attr_key = resolved.and_then(|r| r.resolved_ref);
5049                let attr_type = resolved.and_then(|r| r.resolved_type).or_else(|| {
5050                    attr_key
5051                        .and_then(|k| self.schema_set.arenas.attributes.get(k))
5052                        .and_then(|d| d.resolved_type)
5053                });
5054
5055                // Get fixed value from the attribute use or from the attribute declaration
5056                let fixed = attr_use.attribute.fixed_value.clone().or_else(|| {
5057                    attr_key
5058                        .and_then(|k| self.schema_set.arenas.attributes.get(k))
5059                        .and_then(|d| d.fixed_value.clone())
5060                });
5061
5062                if attr_use.use_kind == AttributeUseKind::Prohibited {
5063                    // In XSD 1.0, use="prohibited" combined with fixed=X is a valid
5064                    // schema construct (constraint au-props-correct.5 was added in XSD 1.1).
5065                    // The combination means the attribute may appear with the fixed value.
5066                    // (W3C test attP031: schema valid in 1.0, instance with fixed value valid.)
5067                    if fixed.is_some() && self.schema_set.is_xsd10() {
5068                        let inheritable = attr_use.attribute.inheritable;
5069                        return AttributeLookup::Found(attr_key, attr_type, fixed, inheritable);
5070                    }
5071                    return AttributeLookup::Prohibited;
5072                }
5073
5074                let inheritable = attr_use.attribute.inheritable;
5075                return AttributeLookup::Found(attr_key, attr_type, fixed, inheritable);
5076            }
5077        }
5078
5079        // Search attribute groups
5080        for ga in self.collect_group_attributes(ct_data) {
5081            if ga.name == local_name && ga.namespace == namespace {
5082                if ga.use_kind == AttributeUseKind::Prohibited {
5083                    // A prohibited use inside an attribute group is transparent —
5084                    // it does NOT propagate the prohibition to the referencing type
5085                    // (W3C Bugzilla #4043 / TSTF conclusion). Skip it and let the
5086                    // base-type chain walk below decide.
5087                    break;
5088                }
5089                #[cfg(feature = "xsd11")]
5090                let inheritable = ga.inheritable;
5091                #[cfg(not(feature = "xsd11"))]
5092                let inheritable = false;
5093                return AttributeLookup::Found(
5094                    ga.attr_key,
5095                    ga.type_key,
5096                    ga.fixed_value,
5097                    inheritable,
5098                );
5099            }
5100        }
5101
5102        // Walk base type chain for inherited attributes (XSD spec §3.4.2.4)
5103        if let Some(TypeKey::Complex(base_ct_key)) = ct_data.resolved_base_type {
5104            if base_ct_key != self.schema_set.any_type_key() {
5105                let base_data = &self.schema_set.arenas.complex_types[base_ct_key];
5106                return self.find_attribute_in_type(base_data, local_name, namespace);
5107            }
5108        }
5109
5110        AttributeLookup::NotFound
5111    }
5112
5113    /// Record inheritable attributes with default/fixed values for propagation
5114    /// to descendant elements (XSD 1.1 §3.3.5.6).
5115    ///
5116    /// Scans both direct attribute uses and attribute group uses. Only records
5117    /// defaults for inheritable attributes that were not explicitly provided.
5118    #[cfg(feature = "xsd11")]
5119    fn record_inheritable_defaults(&mut self, ct_key: ComplexTypeKey) {
5120        use super::context::InheritedAttributeValue;
5121
5122        let ct_data = &self.schema_set.arenas.complex_types[ct_key];
5123
5124        // Collect candidates to avoid borrow conflict with validation_stack
5125        let mut candidates: Vec<(Option<NameId>, NameId, String, Option<AttributeKey>)> =
5126            Vec::new();
5127
5128        // 1. Direct attribute uses
5129        for (i, attr_use) in ct_data.attributes.iter().enumerate() {
5130            if attr_use.use_kind == AttributeUseKind::Prohibited || !attr_use.attribute.inheritable
5131            {
5132                continue;
5133            }
5134            let resolved = ct_data.resolved_attributes.get(i);
5135            let attr_key = resolved.and_then(|r| r.resolved_ref);
5136            let (name, ns) =
5137                self.resolve_attr_use_name_ns(attr_use, resolved, ct_data.target_namespace);
5138            let value = attr_use
5139                .attribute
5140                .default_value
5141                .as_deref()
5142                .or(attr_use.attribute.fixed_value.as_deref())
5143                .or_else(|| {
5144                    attr_key
5145                        .and_then(|k| self.schema_set.arenas.attributes.get(k))
5146                        .and_then(|d| d.default_value.as_deref().or(d.fixed_value.as_deref()))
5147                });
5148            if let Some(val) = value {
5149                candidates.push((ns, name, val.to_string(), attr_key));
5150            }
5151        }
5152
5153        // 2. Attribute group uses
5154        for ga in self.collect_group_attributes(ct_data) {
5155            if ga.use_kind == AttributeUseKind::Prohibited || !ga.inheritable {
5156                continue;
5157            }
5158            let value = ga.default_value.as_deref().or(ga.fixed_value.as_deref());
5159            if let Some(val) = value {
5160                candidates.push((ga.namespace, ga.name, val.to_string(), ga.attr_key));
5161            }
5162        }
5163
5164        // Apply to outgoing_inherited: only for attributes not explicitly
5165        // provided. Use insert() (not or_insert) so defaulted values from
5166        // this element shadow ancestor values per nearest-owner rule.
5167        if let Some(ev) = self.validation_stack.last_mut() {
5168            for (ns, name, val, attr_key) in candidates {
5169                if !ev.seen_attributes.contains(&(ns, name)) {
5170                    ev.outgoing_inherited.insert(
5171                        (ns, name),
5172                        InheritedAttributeValue {
5173                            value: val,
5174                            attribute_key: attr_key,
5175                        },
5176                    );
5177                }
5178            }
5179        }
5180    }
5181
5182    /// Check that all required attributes are present.
5183    ///
5184    /// Walks the base type chain to find inherited required attributes
5185    /// (XSD spec §3.4.2.4). Attributes already declared or prohibited in
5186    /// derived types are skipped via `checked_names`.
5187    fn check_required_attributes(
5188        &mut self,
5189        ct_data: &ComplexTypeDefData,
5190        seen: &HashSet<(Option<NameId>, NameId)>,
5191    ) -> bool {
5192        let mut has_missing = false;
5193        let mut checked_names: HashSet<(Option<NameId>, NameId)> = HashSet::new();
5194
5195        for (i, attr_use) in ct_data.attributes.iter().enumerate() {
5196            let resolved = ct_data.resolved_attributes.get(i);
5197            let (attr_name, attr_ns) =
5198                self.resolve_attr_use_name_ns(attr_use, resolved, ct_data.target_namespace);
5199            checked_names.insert((attr_ns, attr_name));
5200
5201            if attr_use.use_kind != AttributeUseKind::Required {
5202                continue;
5203            }
5204            if !seen.contains(&(attr_ns, attr_name)) {
5205                let name_str = self.schema_set.name_table.resolve(attr_name);
5206                self.report_error(
5207                    "cvc-complex-type.4",
5208                    format!("Required attribute '{}' is missing", name_str),
5209                );
5210                has_missing = true;
5211            }
5212        }
5213
5214        // Check required attributes from attribute groups
5215        for ga in self.collect_group_attributes(ct_data) {
5216            checked_names.insert((ga.namespace, ga.name));
5217            if ga.use_kind != AttributeUseKind::Required {
5218                continue;
5219            }
5220            if !seen.contains(&(ga.namespace, ga.name)) {
5221                let name_str = self.schema_set.name_table.resolve(ga.name);
5222                self.report_error(
5223                    "cvc-complex-type.4",
5224                    format!("Required attribute '{}' is missing", name_str),
5225                );
5226                has_missing = true;
5227            }
5228        }
5229
5230        // Walk base type chain for inherited required attributes
5231        let any_type = self.schema_set.any_type_key();
5232        let mut base_type = ct_data.resolved_base_type;
5233        while let Some(TypeKey::Complex(base_ct_key)) = base_type {
5234            if base_ct_key == any_type {
5235                break;
5236            }
5237            let base_data = &self.schema_set.arenas.complex_types[base_ct_key];
5238
5239            for (i, attr_use) in base_data.attributes.iter().enumerate() {
5240                let resolved = base_data.resolved_attributes.get(i);
5241                let (attr_name, attr_ns) =
5242                    self.resolve_attr_use_name_ns(attr_use, resolved, base_data.target_namespace);
5243                if !checked_names.insert((attr_ns, attr_name)) {
5244                    continue; // already handled by derived type
5245                }
5246                if attr_use.use_kind != AttributeUseKind::Required {
5247                    continue;
5248                }
5249                if !seen.contains(&(attr_ns, attr_name)) {
5250                    let name_str = self.schema_set.name_table.resolve(attr_name);
5251                    self.report_error(
5252                        "cvc-complex-type.4",
5253                        format!("Required attribute '{}' is missing", name_str),
5254                    );
5255                    has_missing = true;
5256                }
5257            }
5258
5259            for ga in self.collect_group_attributes(base_data) {
5260                if !checked_names.insert((ga.namespace, ga.name)) {
5261                    continue;
5262                }
5263                if ga.use_kind != AttributeUseKind::Required {
5264                    continue;
5265                }
5266                if !seen.contains(&(ga.namespace, ga.name)) {
5267                    let name_str = self.schema_set.name_table.resolve(ga.name);
5268                    self.report_error(
5269                        "cvc-complex-type.4",
5270                        format!("Required attribute '{}' is missing", name_str),
5271                    );
5272                    has_missing = true;
5273                }
5274            }
5275
5276            base_type = base_data.resolved_base_type;
5277        }
5278
5279        has_missing
5280    }
5281}
5282
5283/// Resolve an `xml:base` value against an inherited base URI.
5284///
5285/// If the value is already absolute (contains `://` or starts with `/`
5286/// or is a Windows absolute path), it replaces the inherited base.
5287/// Otherwise, it is resolved as a relative URI against the inherited base.
5288fn resolve_base_uri(xml_base: &str, inherited: &str) -> String {
5289    if xml_base.is_empty() {
5290        return inherited.to_string();
5291    }
5292    // Check for absolute URI
5293    if xml_base.contains("://")
5294        || xml_base.starts_with('/')
5295        || (xml_base.len() >= 2 && xml_base.as_bytes().get(1) == Some(&b':'))
5296    {
5297        return xml_base.to_string();
5298    }
5299    if inherited.is_empty() {
5300        return xml_base.to_string();
5301    }
5302    // Resolve relative against inherited base directory.
5303    // Find the last path separator (handles both / and \ for Windows paths).
5304    let last_sep = inherited.rfind('/').or_else(|| inherited.rfind('\\'));
5305    let base_dir = match last_sep {
5306        Some(pos) => &inherited[..=pos],
5307        None => "",
5308    };
5309    if base_dir.is_empty() {
5310        xml_base.to_string()
5311    } else {
5312        format!("{}{}", base_dir, xml_base)
5313    }
5314}
5315
5316#[cfg(test)]
5317#[path = "runtime_tests.rs"]
5318mod tests;