Skip to main content

xsd_schema/types/
sequence.rs

1//! XPath2 sequence types for type matching and conversion
2//!
3//! This module provides the `SequenceType` struct used for:
4//! - Type matching in `instance of` and `treat as` expressions
5//! - Function signatures and parameter types
6//! - Type conversion target specifications
7
8use std::fmt;
9
10use super::XmlTypeCode;
11use crate::ids::{SimpleTypeKey, TypeKey};
12use crate::namespace::qname::QualifiedName;
13use crate::schema::model::DerivationSet;
14use crate::types::value::XmlValue;
15use crate::xpath::cast::type_matches;
16use crate::xpath::context::XPathContext;
17use crate::xpath::iterator::XmlItem;
18use crate::xpath::{DomNavigator, DomNodeType};
19
20/// XPath2 sequence type cardinality
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
22pub enum XmlTypeCardinality {
23    /// Exactly one (T)
24    #[default]
25    One,
26    /// Zero or one (T?)
27    ZeroOrOne,
28    /// One or more (T+)
29    OneOrMore,
30    /// Zero or more (T*)
31    ZeroOrMore,
32    /// Empty sequence only (empty-sequence())
33    Empty,
34}
35
36impl XmlTypeCardinality {
37    /// Check if this cardinality allows empty sequences
38    pub fn allows_empty(&self) -> bool {
39        matches!(self, Self::ZeroOrOne | Self::ZeroOrMore | Self::Empty)
40    }
41
42    /// Check if this cardinality allows multiple items
43    pub fn allows_many(&self) -> bool {
44        matches!(self, Self::OneOrMore | Self::ZeroOrMore)
45    }
46
47    /// Check if an actual count matches this cardinality
48    pub fn matches_count(&self, count: usize) -> bool {
49        match self {
50            Self::One => count == 1,
51            Self::ZeroOrOne => count <= 1,
52            Self::OneOrMore => count >= 1,
53            Self::ZeroOrMore => true,
54            Self::Empty => count == 0,
55        }
56    }
57
58    /// Get the symbol for this cardinality
59    pub fn symbol(&self) -> &'static str {
60        match self {
61            Self::One => "",
62            Self::ZeroOrOne => "?",
63            Self::OneOrMore => "+",
64            Self::ZeroOrMore => "*",
65            Self::Empty => "", // empty-sequence() has no suffix
66        }
67    }
68}
69
70impl fmt::Display for XmlTypeCardinality {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        write!(f, "{}", self.symbol())
73    }
74}
75
76/// Item type in a sequence type
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub enum ItemType {
79    /// item() - any item
80    AnyItem,
81    /// node() - any node
82    AnyNode,
83    /// document-node() with optional element type
84    Document(Option<Box<ItemType>>),
85    /// element() with optional name and type
86    Element(Option<NameTest>, Option<SimpleTypeKey>),
87    /// attribute() with optional name and type
88    Attribute(Option<NameTest>, Option<SimpleTypeKey>),
89    /// schema-element()
90    SchemaElement(QualifiedName),
91    /// schema-attribute()
92    SchemaAttribute(QualifiedName),
93    /// text()
94    Text,
95    /// comment()
96    Comment,
97    /// processing-instruction()
98    ProcessingInstruction(Option<String>),
99    /// namespace-node()
100    NamespaceNode,
101    /// Atomic type (xs:string, xs:integer, etc.)
102    AtomicType(XmlTypeCode),
103    /// Schema-defined atomic type
104    SchemaAtomicType(SimpleTypeKey),
105}
106
107impl ItemType {
108    /// Get the type code for this item type
109    pub fn type_code(&self) -> XmlTypeCode {
110        match self {
111            Self::AnyItem => XmlTypeCode::Item,
112            Self::AnyNode => XmlTypeCode::Node,
113            Self::Document(_) => XmlTypeCode::Document,
114            Self::Element(_, _) | Self::SchemaElement(_) => XmlTypeCode::Element,
115            Self::Attribute(_, _) | Self::SchemaAttribute(_) => XmlTypeCode::Attribute,
116            Self::Text => XmlTypeCode::Text,
117            Self::Comment => XmlTypeCode::Comment,
118            Self::ProcessingInstruction(_) => XmlTypeCode::ProcessingInstruction,
119            Self::NamespaceNode => XmlTypeCode::Namespace,
120            Self::AtomicType(code) => *code,
121            Self::SchemaAtomicType(_) => XmlTypeCode::AnyAtomicType,
122        }
123    }
124
125    /// Check if this item type matches nodes
126    pub fn is_node(&self) -> bool {
127        matches!(
128            self,
129            Self::AnyNode
130                | Self::Document(_)
131                | Self::Element(_, _)
132                | Self::Attribute(_, _)
133                | Self::SchemaElement(_)
134                | Self::SchemaAttribute(_)
135                | Self::Text
136                | Self::Comment
137                | Self::ProcessingInstruction(_)
138                | Self::NamespaceNode
139        )
140    }
141
142    /// Check if this item type matches atomic values
143    pub fn is_atomic(&self) -> bool {
144        matches!(self, Self::AtomicType(_) | Self::SchemaAtomicType(_))
145    }
146
147    /// Check if a single XmlItem matches this item type.
148    ///
149    /// This is the runtime type matching method for function signatures
150    /// and general type checking.
151    pub fn matches_item<N: DomNavigator>(&self, item: &XmlItem<N>, ctx: &XPathContext<'_>) -> bool {
152        match item {
153            XmlItem::Node(nav) => self.matches_node(nav, ctx),
154            XmlItem::Atomic(value) => self.matches_atomic(value, ctx),
155        }
156    }
157
158    /// Check if a node matches this item type.
159    fn matches_node<N: DomNavigator>(&self, nav: &N, ctx: &XPathContext<'_>) -> bool {
160        match self {
161            Self::AnyItem => true,
162            Self::AnyNode => true,
163            Self::Document(None) => nav.node_type() == DomNodeType::Root,
164            Self::Document(Some(inner)) => {
165                if nav.node_type() != DomNodeType::Root {
166                    return false;
167                }
168                // Per XPath 2.0 spec: document-node(E) matches a document node that
169                // contains exactly one element child (optionally with comment/PI nodes),
170                // and that element must match E.
171                let mut cursor = nav.clone();
172                if !cursor.move_to_first_child() {
173                    return false;
174                }
175
176                let mut element_count = 0;
177                let mut matching_element: Option<N> = None;
178
179                loop {
180                    let node_type = cursor.node_type();
181                    match node_type {
182                        DomNodeType::Element => {
183                            element_count += 1;
184                            if element_count > 1 {
185                                // More than one element child - reject
186                                return false;
187                            }
188                            // Store this element to check against inner type
189                            matching_element = Some(cursor.clone());
190                        }
191                        DomNodeType::Comment | DomNodeType::ProcessingInstruction => {
192                            // Comments and PIs are allowed
193                        }
194                        DomNodeType::Text
195                        | DomNodeType::Whitespace
196                        | DomNodeType::SignificantWhitespace => {
197                            // Text nodes in document (outside root element) are typically
198                            // whitespace. Per spec, whitespace-only text nodes may appear.
199                        }
200                        _ => {
201                            // Other node types (shouldn't appear at document level)
202                        }
203                    }
204                    if !cursor.move_to_next_sibling() {
205                        break;
206                    }
207                }
208
209                // Must have exactly one element child
210                if element_count != 1 {
211                    return false;
212                }
213
214                // The single element must match the inner type
215                match matching_element {
216                    Some(elem) => inner.matches_node(&elem, ctx),
217                    None => false,
218                }
219            }
220            Self::Element(name_test, schema_type) => {
221                if nav.node_type() != DomNodeType::Element {
222                    return false;
223                }
224                if let Some(test) = name_test {
225                    if !Self::matches_name_test(test, nav, ctx) {
226                        return false;
227                    }
228                }
229                if let Some(expected) = schema_type {
230                    if !Self::matches_schema_type(nav, *expected, ctx) {
231                        return false;
232                    }
233                }
234                true
235            }
236            Self::Attribute(name_test, schema_type) => {
237                if nav.node_type() != DomNodeType::Attribute {
238                    return false;
239                }
240                if let Some(test) = name_test {
241                    if !Self::matches_name_test(test, nav, ctx) {
242                        return false;
243                    }
244                }
245                if let Some(expected) = schema_type {
246                    if !Self::matches_schema_type(nav, *expected, ctx) {
247                        return false;
248                    }
249                }
250                true
251            }
252            Self::SchemaElement(name) => {
253                if nav.node_type() != DomNodeType::Element {
254                    return false;
255                }
256                if !Self::matches_qname(name, nav, ctx) {
257                    return false;
258                }
259                Self::matches_schema_element_decl(nav, name, ctx)
260            }
261            Self::SchemaAttribute(name) => {
262                if nav.node_type() != DomNodeType::Attribute {
263                    return false;
264                }
265                if !Self::matches_qname(name, nav, ctx) {
266                    return false;
267                }
268                Self::matches_schema_attribute_decl(nav, name, ctx)
269            }
270            Self::Text => nav.node_type().is_text_like(),
271            Self::Comment => nav.node_type() == DomNodeType::Comment,
272            Self::ProcessingInstruction(target) => {
273                nav.node_type() == DomNodeType::ProcessingInstruction
274                    && target.as_ref().is_none_or(|name| nav.local_name() == name)
275            }
276            Self::NamespaceNode => nav.node_type() == DomNodeType::Namespace,
277            // Atomic types don't match nodes
278            Self::AtomicType(_) | Self::SchemaAtomicType(_) => false,
279        }
280    }
281
282    /// Check if an atomic value matches this item type.
283    fn matches_atomic(&self, value: &XmlValue, ctx: &XPathContext<'_>) -> bool {
284        match self {
285            Self::AnyItem => true,
286            Self::AnyNode => false, // node() doesn't match atomics
287            Self::AtomicType(code) => type_matches(value.type_code, *code),
288            Self::SchemaAtomicType(key) => {
289                // Check if value's schema type derives from the expected type
290                if let Some(value_key) = value.schema_type {
291                    if let Some(schema_set) = ctx.schema_set {
292                        return schema_set.is_type_derived_from(
293                            TypeKey::Simple(value_key),
294                            TypeKey::Simple(*key),
295                            DerivationSet::empty(),
296                        );
297                    }
298                    // Fallback: exact match
299                    return value_key == *key;
300                }
301                // No schema type on value - can't match schema-defined type
302                false
303            }
304            // All node types don't match atomics
305            Self::Document(_)
306            | Self::Element(_, _)
307            | Self::Attribute(_, _)
308            | Self::SchemaElement(_)
309            | Self::SchemaAttribute(_)
310            | Self::Text
311            | Self::Comment
312            | Self::ProcessingInstruction(_)
313            | Self::NamespaceNode => false,
314        }
315    }
316
317    /// Helper: check if a node matches a name test.
318    fn matches_name_test<N: DomNavigator>(
319        test: &NameTest,
320        nav: &N,
321        ctx: &XPathContext<'_>,
322    ) -> bool {
323        match test {
324            NameTest::Wildcard => true,
325            NameTest::NamespaceWildcard(local_id) => {
326                // *:local - match any namespace with specific local name
327                match ctx.resolve_name(*local_id) {
328                    Some(local) => nav.local_name() == local,
329                    None => false,
330                }
331            }
332            NameTest::LocalWildcard(ns_id) => {
333                // prefix:* - match any local name in specific namespace
334                match ctx.resolve_name(*ns_id) {
335                    Some(ns) => nav.namespace_uri() == ns,
336                    None => false,
337                }
338            }
339            NameTest::QName(qname) => Self::matches_qname(qname, nav, ctx),
340        }
341    }
342
343    /// Helper: check if a node matches a QualifiedName.
344    fn matches_qname<N: DomNavigator>(
345        qname: &QualifiedName,
346        nav: &N,
347        ctx: &XPathContext<'_>,
348    ) -> bool {
349        let local = match ctx.resolve_name(qname.local_name) {
350            Some(local) => local,
351            None => return false,
352        };
353        let ns = match qname.namespace_uri {
354            Some(id) => match ctx.resolve_name(id) {
355                Some(ns) => ns,
356                None => return false,
357            },
358            None => String::new(),
359        };
360        nav.local_name() == local && nav.namespace_uri() == ns
361    }
362
363    /// Helper: check schema type derivation for element/attribute.
364    fn matches_schema_type<N: DomNavigator>(
365        nav: &N,
366        expected: SimpleTypeKey,
367        ctx: &XPathContext<'_>,
368    ) -> bool {
369        if let Some(actual) = nav.schema_type() {
370            if let Some(schema_set) = ctx.schema_set {
371                return schema_set.is_type_derived_from(
372                    TypeKey::Simple(actual),
373                    TypeKey::Simple(expected),
374                    DerivationSet::empty(),
375                );
376            }
377            // Fallback to equality without schema set
378            return actual == expected;
379        }
380        // No schema type on node
381        false
382    }
383
384    /// Helper: check schema-element() declaration match.
385    fn matches_schema_element_decl<N: DomNavigator>(
386        nav: &N,
387        name: &QualifiedName,
388        ctx: &XPathContext<'_>,
389    ) -> bool {
390        if let Some(schema_set) = ctx.schema_set {
391            let ns_id = name.namespace_uri;
392            let Some(elem_key) = schema_set.lookup_element(ns_id, name.local_name) else {
393                return false;
394            };
395            let Some(elem_data) = schema_set.arenas.elements.get(elem_key) else {
396                return false;
397            };
398            if let Some(expected_type) = elem_data.resolved_type {
399                let Some(actual_type) = nav.schema_type() else {
400                    return false;
401                };
402                return schema_set.is_type_derived_from(
403                    TypeKey::Simple(actual_type),
404                    expected_type,
405                    DerivationSet::empty(),
406                );
407            }
408            // Declaration found, no type constraint
409            return true;
410        }
411        // No schema context - name already verified
412        true
413    }
414
415    /// Helper: check schema-attribute() declaration match.
416    fn matches_schema_attribute_decl<N: DomNavigator>(
417        nav: &N,
418        name: &QualifiedName,
419        ctx: &XPathContext<'_>,
420    ) -> bool {
421        if let Some(schema_set) = ctx.schema_set {
422            let ns_id = name.namespace_uri;
423            let Some(attr_key) = schema_set.lookup_attribute(ns_id, name.local_name) else {
424                return false;
425            };
426            let Some(attr_data) = schema_set.arenas.attributes.get(attr_key) else {
427                return false;
428            };
429            if let Some(expected_type) = attr_data.resolved_type {
430                let Some(actual_type) = nav.schema_type() else {
431                    return false;
432                };
433                return schema_set.is_type_derived_from(
434                    TypeKey::Simple(actual_type),
435                    expected_type,
436                    DerivationSet::empty(),
437                );
438            }
439            // Declaration found, no type constraint
440            return true;
441        }
442        // No schema context - name already verified
443        true
444    }
445}
446
447/// Name test for element/attribute tests (resolved form with interned names)
448#[derive(Debug, Clone, PartialEq, Eq)]
449pub enum NameTest {
450    /// Wildcard (*) - matches any name
451    Wildcard,
452    /// Namespace wildcard (*:local) - matches any namespace with specific local name
453    /// The NameId is the interned local name
454    NamespaceWildcard(crate::ids::NameId),
455    /// Local name wildcard (prefix:*) - matches any local name in namespace
456    /// The NameId is the resolved namespace URI
457    LocalWildcard(crate::ids::NameId),
458    /// Specific QName (fully resolved)
459    QName(QualifiedName),
460}
461
462impl NameTest {
463    /// Check if this is a wildcard (matches any name)
464    pub fn is_wildcard(&self) -> bool {
465        matches!(self, Self::Wildcard)
466    }
467}
468
469/// XPath2 sequence type
470///
471/// Describes the expected type of a sequence of items.
472/// Used for type checking, type matching, and conversions.
473#[derive(Debug, Clone, PartialEq, Eq)]
474pub struct SequenceType {
475    /// The item type
476    pub item_type: ItemType,
477    /// The occurrence indicator
478    pub cardinality: XmlTypeCardinality,
479}
480
481impl SequenceType {
482    /// Create a new sequence type
483    pub fn new(item_type: ItemType, cardinality: XmlTypeCardinality) -> Self {
484        Self {
485            item_type,
486            cardinality,
487        }
488    }
489
490    /// Create a sequence type for exactly one item
491    pub fn one(item_type: ItemType) -> Self {
492        Self::new(item_type, XmlTypeCardinality::One)
493    }
494
495    /// Create a sequence type for zero or one items
496    pub fn optional(item_type: ItemType) -> Self {
497        Self::new(item_type, XmlTypeCardinality::ZeroOrOne)
498    }
499
500    /// Create a sequence type for one or more items
501    pub fn plus(item_type: ItemType) -> Self {
502        Self::new(item_type, XmlTypeCardinality::OneOrMore)
503    }
504
505    /// Create a sequence type for zero or more items
506    pub fn star(item_type: ItemType) -> Self {
507        Self::new(item_type, XmlTypeCardinality::ZeroOrMore)
508    }
509
510    /// Create empty-sequence() type
511    ///
512    /// This only matches empty sequences (zero items).
513    pub fn empty() -> Self {
514        Self::new(ItemType::AnyItem, XmlTypeCardinality::Empty)
515    }
516
517    /// Check if this is the empty-sequence() type
518    pub fn is_empty_sequence(&self) -> bool {
519        self.cardinality == XmlTypeCardinality::Empty
520    }
521
522    /// Create item()* - any sequence
523    pub fn any() -> Self {
524        Self::star(ItemType::AnyItem)
525    }
526
527    /// Create item() - exactly one item
528    pub fn item() -> Self {
529        Self::one(ItemType::AnyItem)
530    }
531
532    /// Create node() - exactly one node
533    pub fn node() -> Self {
534        Self::one(ItemType::AnyNode)
535    }
536
537    /// Create node()* - sequence of nodes
538    pub fn nodes() -> Self {
539        Self::star(ItemType::AnyNode)
540    }
541
542    // Convenience constructors for common atomic types
543
544    /// xs:string
545    pub fn string() -> Self {
546        Self::one(ItemType::AtomicType(XmlTypeCode::String))
547    }
548
549    /// xs:string?
550    pub fn string_optional() -> Self {
551        Self::optional(ItemType::AtomicType(XmlTypeCode::String))
552    }
553
554    /// xs:boolean
555    pub fn boolean() -> Self {
556        Self::one(ItemType::AtomicType(XmlTypeCode::Boolean))
557    }
558
559    /// xs:integer
560    pub fn integer() -> Self {
561        Self::one(ItemType::AtomicType(XmlTypeCode::Integer))
562    }
563
564    /// xs:integer?
565    pub fn integer_optional() -> Self {
566        Self::optional(ItemType::AtomicType(XmlTypeCode::Integer))
567    }
568
569    /// xs:decimal
570    pub fn decimal() -> Self {
571        Self::one(ItemType::AtomicType(XmlTypeCode::Decimal))
572    }
573
574    /// xs:double
575    pub fn double() -> Self {
576        Self::one(ItemType::AtomicType(XmlTypeCode::Double))
577    }
578
579    /// xs:double?
580    pub fn double_optional() -> Self {
581        Self::optional(ItemType::AtomicType(XmlTypeCode::Double))
582    }
583
584    /// xs:anyAtomicType
585    pub fn any_atomic() -> Self {
586        Self::one(ItemType::AtomicType(XmlTypeCode::AnyAtomicType))
587    }
588
589    /// xs:anyAtomicType?
590    pub fn any_atomic_optional() -> Self {
591        Self::optional(ItemType::AtomicType(XmlTypeCode::AnyAtomicType))
592    }
593
594    /// xs:anyAtomicType*
595    pub fn any_atomic_star() -> Self {
596        Self::star(ItemType::AtomicType(XmlTypeCode::AnyAtomicType))
597    }
598
599    /// xs:dateTime
600    pub fn datetime() -> Self {
601        Self::one(ItemType::AtomicType(XmlTypeCode::DateTime))
602    }
603
604    /// xs:date
605    pub fn date() -> Self {
606        Self::one(ItemType::AtomicType(XmlTypeCode::Date))
607    }
608
609    /// xs:time
610    pub fn time() -> Self {
611        Self::one(ItemType::AtomicType(XmlTypeCode::Time))
612    }
613
614    /// xs:duration
615    pub fn duration() -> Self {
616        Self::one(ItemType::AtomicType(XmlTypeCode::Duration))
617    }
618
619    /// xs:QName
620    pub fn qname() -> Self {
621        Self::one(ItemType::AtomicType(XmlTypeCode::QName))
622    }
623
624    /// xs:anyURI
625    pub fn any_uri() -> Self {
626        Self::one(ItemType::AtomicType(XmlTypeCode::AnyUri))
627    }
628
629    // Query methods
630
631    /// Get the type code for the item type
632    pub fn type_code(&self) -> XmlTypeCode {
633        self.item_type.type_code()
634    }
635
636    /// Check if this sequence type matches nodes
637    pub fn is_node(&self) -> bool {
638        self.item_type.is_node()
639    }
640
641    /// Check if this sequence type matches atomic values
642    pub fn is_atomic(&self) -> bool {
643        self.item_type.is_atomic()
644    }
645
646    /// Check if this sequence type is numeric (decimal, float, double, integer types)
647    pub fn is_numeric(&self) -> bool {
648        self.type_code().is_numeric()
649    }
650
651    /// Check if this sequence type allows empty sequences
652    pub fn allows_empty(&self) -> bool {
653        self.cardinality.allows_empty()
654    }
655
656    /// Check if a sequence of items matches this sequence type.
657    ///
658    /// Validates both cardinality and item type for each item in the sequence.
659    pub fn matches_sequence<N: DomNavigator>(
660        &self,
661        items: &[XmlItem<N>],
662        ctx: &XPathContext<'_>,
663    ) -> bool {
664        // Check cardinality first
665        if !self.cardinality.matches_count(items.len()) {
666            return false;
667        }
668        // Check each item matches the item type
669        for item in items {
670            if !self.item_type.matches_item(item, ctx) {
671                return false;
672            }
673        }
674        true
675    }
676}
677
678impl Default for SequenceType {
679    fn default() -> Self {
680        Self::any()
681    }
682}
683
684impl fmt::Display for SequenceType {
685    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
686        // Handle empty-sequence() specially
687        if self.cardinality == XmlTypeCardinality::Empty {
688            return write!(f, "empty-sequence()");
689        }
690
691        // Format item type
692        match &self.item_type {
693            ItemType::AnyItem => write!(f, "item()")?,
694            ItemType::AnyNode => write!(f, "node()")?,
695            ItemType::Document(None) => write!(f, "document-node()")?,
696            ItemType::Document(Some(elem)) => write!(f, "document-node({:?})", elem)?,
697            ItemType::Element(None, None) => write!(f, "element()")?,
698            ItemType::Element(Some(name), None) => write!(f, "element({:?})", name)?,
699            ItemType::Element(name, Some(_type_key)) => {
700                write!(f, "element({:?}, ...)", name)?;
701            }
702            ItemType::Attribute(None, None) => write!(f, "attribute()")?,
703            ItemType::Attribute(Some(name), None) => write!(f, "attribute({:?})", name)?,
704            ItemType::Attribute(name, Some(_type_key)) => {
705                write!(f, "attribute({:?}, ...)", name)?;
706            }
707            ItemType::SchemaElement(qn) => write!(f, "schema-element({:?})", qn)?,
708            ItemType::SchemaAttribute(qn) => write!(f, "schema-attribute({:?})", qn)?,
709            ItemType::Text => write!(f, "text()")?,
710            ItemType::Comment => write!(f, "comment()")?,
711            ItemType::ProcessingInstruction(None) => write!(f, "processing-instruction()")?,
712            ItemType::ProcessingInstruction(Some(name)) => {
713                write!(f, "processing-instruction({})", name)?;
714            }
715            ItemType::NamespaceNode => write!(f, "namespace-node()")?,
716            ItemType::AtomicType(code) => {
717                if let Some(name) = code.local_name() {
718                    write!(f, "xs:{}", name)?;
719                } else {
720                    write!(f, "{:?}", code)?;
721                }
722            }
723            ItemType::SchemaAtomicType(_key) => write!(f, "schema-type(...)")?,
724        }
725
726        // Format cardinality
727        write!(f, "{}", self.cardinality)?;
728
729        Ok(())
730    }
731}
732
733// ============================================================================
734// Helpers (xsd11)
735// ============================================================================
736
737/// Resolve the item type's `SimpleTypeKey` from a list type definition.
738///
739/// Given a list type's `SimpleTypeKey`, looks up its `resolved_item_type` and
740/// extracts the `SimpleTypeKey` (if it refers to a simple type).
741/// Returns `None` if the type is not found or item type is not a simple type.
742#[cfg(feature = "xsd11")]
743pub fn resolve_list_item_schema_type(
744    list_type_key: SimpleTypeKey,
745    schema_set: &crate::schema::SchemaSet,
746) -> Option<SimpleTypeKey> {
747    let st_data = schema_set.arenas.simple_types.get(list_type_key)?;
748    st_data.resolved_item_type?.as_simple()
749}
750
751// ============================================================================
752// Tests
753// ============================================================================
754
755#[cfg(test)]
756mod tests {
757    use super::*;
758    use num_bigint::BigInt;
759
760    use crate::namespace::table::NameTable;
761    use crate::navigator::RoXmlNavigator;
762
763    #[test]
764    fn test_cardinality_matches_count() {
765        assert!(XmlTypeCardinality::One.matches_count(1));
766        assert!(!XmlTypeCardinality::One.matches_count(0));
767        assert!(!XmlTypeCardinality::One.matches_count(2));
768
769        assert!(XmlTypeCardinality::ZeroOrOne.matches_count(0));
770        assert!(XmlTypeCardinality::ZeroOrOne.matches_count(1));
771        assert!(!XmlTypeCardinality::ZeroOrOne.matches_count(2));
772
773        assert!(!XmlTypeCardinality::OneOrMore.matches_count(0));
774        assert!(XmlTypeCardinality::OneOrMore.matches_count(1));
775        assert!(XmlTypeCardinality::OneOrMore.matches_count(100));
776
777        assert!(XmlTypeCardinality::ZeroOrMore.matches_count(0));
778        assert!(XmlTypeCardinality::ZeroOrMore.matches_count(1));
779        assert!(XmlTypeCardinality::ZeroOrMore.matches_count(100));
780    }
781
782    #[test]
783    fn test_cardinality_symbols() {
784        assert_eq!(XmlTypeCardinality::One.symbol(), "");
785        assert_eq!(XmlTypeCardinality::ZeroOrOne.symbol(), "?");
786        assert_eq!(XmlTypeCardinality::OneOrMore.symbol(), "+");
787        assert_eq!(XmlTypeCardinality::ZeroOrMore.symbol(), "*");
788    }
789
790    #[test]
791    fn test_sequence_type_display() {
792        assert_eq!(SequenceType::item().to_string(), "item()");
793        assert_eq!(SequenceType::string().to_string(), "xs:string");
794        assert_eq!(SequenceType::string_optional().to_string(), "xs:string?");
795        assert_eq!(SequenceType::nodes().to_string(), "node()*");
796        assert_eq!(SequenceType::integer().to_string(), "xs:integer");
797    }
798
799    #[test]
800    fn test_sequence_type_is_node() {
801        assert!(SequenceType::node().is_node());
802        assert!(SequenceType::nodes().is_node());
803        assert!(!SequenceType::string().is_node());
804        assert!(!SequenceType::any_atomic().is_node());
805    }
806
807    #[test]
808    fn test_sequence_type_is_atomic() {
809        assert!(SequenceType::string().is_atomic());
810        assert!(SequenceType::integer().is_atomic());
811        assert!(SequenceType::any_atomic().is_atomic());
812        assert!(!SequenceType::node().is_atomic());
813        assert!(!SequenceType::item().is_atomic());
814    }
815
816    #[test]
817    fn test_sequence_type_is_numeric() {
818        assert!(SequenceType::decimal().is_numeric());
819        assert!(SequenceType::integer().is_numeric());
820        assert!(SequenceType::double().is_numeric());
821        assert!(!SequenceType::string().is_numeric());
822        assert!(!SequenceType::boolean().is_numeric());
823    }
824
825    #[test]
826    fn test_item_type_type_code() {
827        assert_eq!(ItemType::AnyItem.type_code(), XmlTypeCode::Item);
828        assert_eq!(ItemType::AnyNode.type_code(), XmlTypeCode::Node);
829        assert_eq!(ItemType::Text.type_code(), XmlTypeCode::Text);
830        assert_eq!(
831            ItemType::AtomicType(XmlTypeCode::String).type_code(),
832            XmlTypeCode::String
833        );
834    }
835
836    // ============================================================================
837    // Runtime Sequence Type Matching Tests
838    // ============================================================================
839
840    #[test]
841    fn test_cardinality_one() {
842        // xs:integer requires exactly 1 item
843        let seq_type = SequenceType::integer();
844        let table = NameTable::new();
845        let ctx = XPathContext::new(&table);
846
847        let one_item: Vec<XmlItem<RoXmlNavigator<'static>>> =
848            vec![XmlItem::Atomic(XmlValue::integer(BigInt::from(42)))];
849        let no_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![];
850        let two_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
851            XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
852            XmlItem::Atomic(XmlValue::integer(BigInt::from(2))),
853        ];
854
855        assert!(seq_type.matches_sequence(&one_item, &ctx));
856        assert!(!seq_type.matches_sequence(&no_items, &ctx));
857        assert!(!seq_type.matches_sequence(&two_items, &ctx));
858    }
859
860    #[test]
861    fn test_cardinality_optional() {
862        // xs:integer? allows 0 or 1 item
863        let seq_type = SequenceType::integer_optional();
864        let table = NameTable::new();
865        let ctx = XPathContext::new(&table);
866
867        let one_item: Vec<XmlItem<RoXmlNavigator<'static>>> =
868            vec![XmlItem::Atomic(XmlValue::integer(BigInt::from(42)))];
869        let no_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![];
870        let two_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
871            XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
872            XmlItem::Atomic(XmlValue::integer(BigInt::from(2))),
873        ];
874
875        assert!(seq_type.matches_sequence(&one_item, &ctx));
876        assert!(seq_type.matches_sequence(&no_items, &ctx));
877        assert!(!seq_type.matches_sequence(&two_items, &ctx));
878    }
879
880    #[test]
881    fn test_cardinality_star() {
882        // xs:integer* allows any count
883        let seq_type = SequenceType::star(ItemType::AtomicType(XmlTypeCode::Integer));
884        let table = NameTable::new();
885        let ctx = XPathContext::new(&table);
886
887        let no_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![];
888        let one_item: Vec<XmlItem<RoXmlNavigator<'static>>> =
889            vec![XmlItem::Atomic(XmlValue::integer(BigInt::from(42)))];
890        let three_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
891            XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
892            XmlItem::Atomic(XmlValue::integer(BigInt::from(2))),
893            XmlItem::Atomic(XmlValue::integer(BigInt::from(3))),
894        ];
895
896        assert!(seq_type.matches_sequence(&no_items, &ctx));
897        assert!(seq_type.matches_sequence(&one_item, &ctx));
898        assert!(seq_type.matches_sequence(&three_items, &ctx));
899    }
900
901    #[test]
902    fn test_cardinality_plus() {
903        // xs:integer+ requires at least 1 item
904        let seq_type = SequenceType::plus(ItemType::AtomicType(XmlTypeCode::Integer));
905        let table = NameTable::new();
906        let ctx = XPathContext::new(&table);
907
908        let no_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![];
909        let one_item: Vec<XmlItem<RoXmlNavigator<'static>>> =
910            vec![XmlItem::Atomic(XmlValue::integer(BigInt::from(42)))];
911        let three_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
912            XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
913            XmlItem::Atomic(XmlValue::integer(BigInt::from(2))),
914            XmlItem::Atomic(XmlValue::integer(BigInt::from(3))),
915        ];
916
917        assert!(!seq_type.matches_sequence(&no_items, &ctx));
918        assert!(seq_type.matches_sequence(&one_item, &ctx));
919        assert!(seq_type.matches_sequence(&three_items, &ctx));
920    }
921
922    #[test]
923    fn test_atomic_integer_match() {
924        // Integer value matches xs:integer
925        let item_type = ItemType::AtomicType(XmlTypeCode::Integer);
926        let table = NameTable::new();
927        let ctx = XPathContext::new(&table);
928
929        let int_value: XmlItem<RoXmlNavigator<'static>> =
930            XmlItem::Atomic(XmlValue::integer(BigInt::from(42)));
931        let str_value: XmlItem<RoXmlNavigator<'static>> =
932            XmlItem::Atomic(XmlValue::string("hello"));
933
934        assert!(item_type.matches_item(&int_value, &ctx));
935        assert!(!item_type.matches_item(&str_value, &ctx));
936    }
937
938    #[test]
939    fn test_atomic_string_match() {
940        // String value matches xs:string
941        let item_type = ItemType::AtomicType(XmlTypeCode::String);
942        let table = NameTable::new();
943        let ctx = XPathContext::new(&table);
944
945        let str_value: XmlItem<RoXmlNavigator<'static>> =
946            XmlItem::Atomic(XmlValue::string("hello"));
947        let int_value: XmlItem<RoXmlNavigator<'static>> =
948            XmlItem::Atomic(XmlValue::integer(BigInt::from(42)));
949
950        assert!(item_type.matches_item(&str_value, &ctx));
951        assert!(!item_type.matches_item(&int_value, &ctx));
952    }
953
954    #[test]
955    fn test_atomic_type_hierarchy() {
956        // Any atomic matches xs:anyAtomicType
957        let item_type = ItemType::AtomicType(XmlTypeCode::AnyAtomicType);
958        let table = NameTable::new();
959        let ctx = XPathContext::new(&table);
960
961        let str_value: XmlItem<RoXmlNavigator<'static>> =
962            XmlItem::Atomic(XmlValue::string("hello"));
963        let int_value: XmlItem<RoXmlNavigator<'static>> =
964            XmlItem::Atomic(XmlValue::integer(BigInt::from(42)));
965        let bool_value: XmlItem<RoXmlNavigator<'static>> = XmlItem::Atomic(XmlValue::boolean(true));
966
967        assert!(item_type.matches_item(&str_value, &ctx));
968        assert!(item_type.matches_item(&int_value, &ctx));
969        assert!(item_type.matches_item(&bool_value, &ctx));
970    }
971
972    #[test]
973    fn test_item_matches_any() {
974        // item() matches both nodes and atomics
975        let item_type = ItemType::AnyItem;
976        let table = NameTable::new();
977        let ctx = XPathContext::new(&table);
978
979        let doc = roxmltree::Document::parse("<root/>").expect("parse xml");
980        let mut nav = RoXmlNavigator::new(&doc);
981        nav.move_to_first_child();
982
983        let node_item = XmlItem::Node(nav);
984        let atomic_item: XmlItem<RoXmlNavigator<'_>> = XmlItem::Atomic(XmlValue::string("hello"));
985
986        assert!(item_type.matches_item(&node_item, &ctx));
987        assert!(item_type.matches_item(&atomic_item, &ctx));
988    }
989
990    #[test]
991    fn test_node_rejects_atomic() {
992        // node() rejects atomic values
993        let item_type = ItemType::AnyNode;
994        let table = NameTable::new();
995        let ctx = XPathContext::new(&table);
996
997        let atomic_item: XmlItem<RoXmlNavigator<'static>> =
998            XmlItem::Atomic(XmlValue::string("hello"));
999
1000        assert!(!item_type.matches_item(&atomic_item, &ctx));
1001    }
1002
1003    #[test]
1004    fn test_atomic_rejects_node() {
1005        // xs:integer rejects nodes
1006        let item_type = ItemType::AtomicType(XmlTypeCode::Integer);
1007        let table = NameTable::new();
1008        let ctx = XPathContext::new(&table);
1009
1010        let doc = roxmltree::Document::parse("<root/>").expect("parse xml");
1011        let mut nav = RoXmlNavigator::new(&doc);
1012        nav.move_to_first_child();
1013
1014        let node_item = XmlItem::Node(nav);
1015
1016        assert!(!item_type.matches_item(&node_item, &ctx));
1017    }
1018
1019    #[test]
1020    fn test_element_item_type_matches_element() {
1021        // element() matches element nodes
1022        let item_type = ItemType::Element(None, None);
1023        let table = NameTable::new();
1024        let ctx = XPathContext::new(&table);
1025
1026        let doc = roxmltree::Document::parse("<root/>").expect("parse xml");
1027        let mut nav = RoXmlNavigator::new(&doc);
1028        nav.move_to_first_child();
1029
1030        let node_item = XmlItem::Node(nav);
1031
1032        assert!(item_type.matches_item(&node_item, &ctx));
1033    }
1034
1035    #[test]
1036    fn test_text_item_type_matches_text() {
1037        // text() matches text nodes
1038        let item_type = ItemType::Text;
1039        let table = NameTable::new();
1040        let ctx = XPathContext::new(&table);
1041
1042        let doc = roxmltree::Document::parse("<root>text</root>").expect("parse xml");
1043        let mut nav = RoXmlNavigator::new(&doc);
1044        nav.move_to_first_child(); // root
1045        nav.move_to_first_child(); // text
1046
1047        let node_item = XmlItem::Node(nav);
1048
1049        assert!(item_type.matches_item(&node_item, &ctx));
1050    }
1051
1052    #[test]
1053    fn test_sequence_type_mixed_types_fail() {
1054        // Sequence with wrong type fails
1055        let seq_type = SequenceType::star(ItemType::AtomicType(XmlTypeCode::Integer));
1056        let table = NameTable::new();
1057        let ctx = XPathContext::new(&table);
1058
1059        let mixed_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
1060            XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
1061            XmlItem::Atomic(XmlValue::string("not an integer")),
1062        ];
1063
1064        assert!(!seq_type.matches_sequence(&mixed_items, &ctx));
1065    }
1066
1067    #[test]
1068    fn test_integer_derived_types() {
1069        // Integer derived types match xs:integer via type_matches
1070        let item_type = ItemType::AtomicType(XmlTypeCode::Integer);
1071        let table = NameTable::new();
1072        let ctx = XPathContext::new(&table);
1073
1074        // Create a value with integer type code
1075        let int_value: XmlItem<RoXmlNavigator<'static>> =
1076            XmlItem::Atomic(XmlValue::integer(BigInt::from(42)));
1077
1078        assert!(item_type.matches_item(&int_value, &ctx));
1079    }
1080
1081    // ============================================================================
1082    // empty-sequence() Tests
1083    // ============================================================================
1084
1085    #[test]
1086    fn test_empty_sequence_matches_empty() {
1087        // empty-sequence() only matches empty sequences
1088        let seq_type = SequenceType::empty();
1089        let table = NameTable::new();
1090        let ctx = XPathContext::new(&table);
1091
1092        let empty: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![];
1093        assert!(seq_type.matches_sequence(&empty, &ctx));
1094    }
1095
1096    #[test]
1097    fn test_empty_sequence_rejects_non_empty() {
1098        // empty-sequence() rejects non-empty sequences
1099        let seq_type = SequenceType::empty();
1100        let table = NameTable::new();
1101        let ctx = XPathContext::new(&table);
1102
1103        let one_item: Vec<XmlItem<RoXmlNavigator<'static>>> =
1104            vec![XmlItem::Atomic(XmlValue::integer(BigInt::from(42)))];
1105        let two_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
1106            XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
1107            XmlItem::Atomic(XmlValue::integer(BigInt::from(2))),
1108        ];
1109
1110        assert!(!seq_type.matches_sequence(&one_item, &ctx));
1111        assert!(!seq_type.matches_sequence(&two_items, &ctx));
1112    }
1113
1114    #[test]
1115    fn test_empty_sequence_display() {
1116        // empty-sequence() displays correctly
1117        assert_eq!(SequenceType::empty().to_string(), "empty-sequence()");
1118    }
1119
1120    #[test]
1121    fn test_is_empty_sequence() {
1122        assert!(SequenceType::empty().is_empty_sequence());
1123        assert!(!SequenceType::any().is_empty_sequence());
1124        assert!(!SequenceType::integer().is_empty_sequence());
1125    }
1126
1127    // ============================================================================
1128    // document-node(E) Tests
1129    // ============================================================================
1130
1131    #[test]
1132    fn test_document_node_with_element_single_element() {
1133        // document-node(element()) matches document with exactly one element child
1134        let item_type = ItemType::Document(Some(Box::new(ItemType::Element(None, None))));
1135        let table = NameTable::new();
1136        let ctx = XPathContext::new(&table);
1137
1138        let doc = roxmltree::Document::parse("<root/>").expect("parse xml");
1139        let nav = RoXmlNavigator::new(&doc);
1140
1141        assert!(item_type.matches_node(&nav, &ctx));
1142    }
1143
1144    #[test]
1145    fn test_document_node_with_element_allows_comments() {
1146        // document-node(element()) allows comments alongside the element
1147        let item_type = ItemType::Document(Some(Box::new(ItemType::Element(None, None))));
1148        let table = NameTable::new();
1149        let ctx = XPathContext::new(&table);
1150
1151        let doc = roxmltree::Document::parse("<!-- comment --><root/><!-- another -->")
1152            .expect("parse xml");
1153        let nav = RoXmlNavigator::new(&doc);
1154
1155        assert!(item_type.matches_node(&nav, &ctx));
1156    }
1157
1158    #[test]
1159    fn test_document_node_with_element_allows_pi() {
1160        // document-node(element()) allows processing instructions alongside the element
1161        let item_type = ItemType::Document(Some(Box::new(ItemType::Element(None, None))));
1162        let table = NameTable::new();
1163        let ctx = XPathContext::new(&table);
1164
1165        let doc = roxmltree::Document::parse("<?target data?><root/>").expect("parse xml");
1166        let nav = RoXmlNavigator::new(&doc);
1167
1168        assert!(item_type.matches_node(&nav, &ctx));
1169    }
1170
1171    #[test]
1172    fn test_document_node_with_element_rejects_no_element() {
1173        // document-node(element()) rejects document with no element children
1174        let item_type = ItemType::Document(Some(Box::new(ItemType::Element(None, None))));
1175        let table = NameTable::new();
1176        let ctx = XPathContext::new(&table);
1177
1178        // A document with only a comment (no root element) - this would be malformed XML
1179        // but we can test the logic by checking a document node directly
1180        let doc = roxmltree::Document::parse("<!-- comment only is invalid XML --><dummy/>")
1181            .expect("parse xml");
1182        let nav = RoXmlNavigator::new(&doc);
1183
1184        // This should match since there's still an element
1185        assert!(item_type.matches_node(&nav, &ctx));
1186    }
1187
1188    #[test]
1189    fn test_document_node_with_specific_element_name() {
1190        // document-node(element(root)) matches document with element named "root"
1191        let table = NameTable::new();
1192        let root_id = table.add("root");
1193        let qname = QualifiedName::local(root_id);
1194        let name_test = NameTest::QName(qname);
1195        let item_type =
1196            ItemType::Document(Some(Box::new(ItemType::Element(Some(name_test), None))));
1197        let ctx = XPathContext::new(&table);
1198
1199        let doc = roxmltree::Document::parse("<root/>").expect("parse xml");
1200        let nav = RoXmlNavigator::new(&doc);
1201
1202        assert!(item_type.matches_node(&nav, &ctx));
1203    }
1204
1205    #[test]
1206    fn test_document_node_with_wrong_element_name() {
1207        // document-node(element(root)) rejects document with element named "other"
1208        let table = NameTable::new();
1209        let root_id = table.add("root");
1210        let qname = QualifiedName::local(root_id);
1211        let name_test = NameTest::QName(qname);
1212        let item_type =
1213            ItemType::Document(Some(Box::new(ItemType::Element(Some(name_test), None))));
1214        let ctx = XPathContext::new(&table);
1215
1216        let doc = roxmltree::Document::parse("<other/>").expect("parse xml");
1217        let nav = RoXmlNavigator::new(&doc);
1218
1219        assert!(!item_type.matches_node(&nav, &ctx));
1220    }
1221}