Skip to main content

xsd_schema/types/
complex.rs

1//! Complex type definitions
2//!
3//! This module implements XSD complex type definitions with content models,
4//! attributes, and derivation mechanisms.
5
6use crate::ids::{
7    AttributeGroupKey, AttributeKey, ComplexTypeKey, ElementKey, ModelGroupKey, NameId,
8    SimpleTypeKey, TypeKey,
9};
10use crate::parser::location::SourceRef;
11use crate::schema::model::{DerivationSet, XsdVersion};
12
13/// Complex type content kind
14#[derive(Debug, Clone, PartialEq, Eq, Default)]
15pub enum ContentKind {
16    /// Empty content (no child elements or text)
17    #[default]
18    Empty,
19    /// Simple content (text only, possibly with attributes)
20    Simple,
21    /// Element-only content (child elements, no mixed text)
22    ElementOnly,
23    /// Mixed content (child elements with interleaved text)
24    Mixed,
25}
26
27/// Derivation method for complex types
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum DerivationMethod {
30    /// Derived by restriction (constraining base type)
31    Restriction,
32    /// Derived by extension (adding to base type)
33    Extension,
34}
35
36/// Reference to a type (simple or complex)
37#[derive(Debug, Clone)]
38pub enum TypeRef {
39    /// Reference to a simple type
40    Simple(SimpleTypeRef),
41    /// Reference to a complex type
42    Complex(ComplexTypeRef),
43    /// Unresolved reference (QName)
44    Unresolved {
45        namespace: Option<NameId>,
46        local_name: NameId,
47    },
48    /// Built-in anyType
49    AnyType,
50}
51
52/// Reference to a simple type
53#[derive(Debug, Clone)]
54pub enum SimpleTypeRef {
55    /// Resolved reference
56    Resolved(SimpleTypeKey),
57    /// Built-in type
58    BuiltIn(super::simple::BuiltInType),
59}
60
61/// Reference to a complex type
62#[derive(Debug, Clone)]
63pub enum ComplexTypeRef {
64    /// Resolved reference
65    Resolved(ComplexTypeKey),
66    /// Built-in anyType (the ur-type)
67    AnyType,
68}
69
70/// Content model for complex types
71#[derive(Debug, Clone, Default)]
72pub enum ComplexTypeContent {
73    /// Empty content (no elements or text)
74    #[default]
75    Empty,
76
77    /// Simple content (text value with attributes)
78    Simple(SimpleContentDef),
79
80    /// Complex content (elements with optional mixed text)
81    Complex(Box<ComplexContentDef>),
82}
83
84/// Simple content definition (text value with attributes)
85#[derive(Debug, Clone)]
86pub struct SimpleContentDef {
87    /// Base type for the text content
88    pub base_type: SimpleTypeRef,
89
90    /// Derivation method
91    pub derivation: DerivationMethod,
92
93    /// Source location
94    pub source: Option<SourceRef>,
95}
96
97/// Complex content definition (elements and optionally mixed text)
98#[derive(Debug, Clone)]
99pub struct ComplexContentDef {
100    /// Content model particle (sequence, choice, all, group ref)
101    pub particle: Option<ContentParticle>,
102
103    /// Derivation method
104    pub derivation: DerivationMethod,
105
106    /// Whether mixed content is allowed
107    pub mixed: bool,
108
109    /// Source location
110    pub source: Option<SourceRef>,
111
112    /// XSD 1.1: Open content (runtime matching implemented; schema-level validation pending)
113    pub open_content: Option<OpenContent>,
114}
115
116/// Content particle (term with occurrence constraints)
117#[derive(Debug, Clone)]
118pub struct ContentParticle {
119    /// The term (element, group, wildcard)
120    pub term: ContentTerm,
121
122    /// Minimum occurrences (default 1)
123    pub min_occurs: u32,
124
125    /// Maximum occurrences (None = unbounded)
126    pub max_occurs: Option<u32>,
127
128    /// Source location
129    pub source: Option<SourceRef>,
130}
131
132impl ContentParticle {
133    /// Create a particle with default occurrence (1..1)
134    pub fn new(term: ContentTerm) -> Self {
135        Self {
136            term,
137            min_occurs: 1,
138            max_occurs: Some(1),
139            source: None,
140        }
141    }
142
143    /// Create a particle with custom occurrence
144    pub fn with_occurs(term: ContentTerm, min: u32, max: Option<u32>) -> Self {
145        Self {
146            term,
147            min_occurs: min,
148            max_occurs: max,
149            source: None,
150        }
151    }
152
153    /// Check if this particle is optional (minOccurs=0)
154    pub fn is_optional(&self) -> bool {
155        self.min_occurs == 0
156    }
157
158    /// Check if this particle allows multiple occurrences
159    pub fn is_repeating(&self) -> bool {
160        self.max_occurs.is_none_or(|max| max > 1)
161    }
162
163    /// Check if this particle is unbounded
164    pub fn is_unbounded(&self) -> bool {
165        self.max_occurs.is_none()
166    }
167}
168
169/// Content model term (element, group, or wildcard)
170#[derive(Debug, Clone)]
171pub enum ContentTerm {
172    /// Reference to an element
173    Element(ElementRef),
174
175    /// Model group (sequence, choice, all)
176    Group(ModelGroupDef),
177
178    /// Reference to a named model group
179    GroupRef(ModelGroupKey),
180
181    /// Wildcard (any element)
182    Wildcard(WildcardRef),
183}
184
185/// Reference to an element
186#[derive(Debug, Clone)]
187pub enum ElementRef {
188    /// Local element declaration
189    Local(ElementKey),
190    /// Reference to a global element
191    Global {
192        namespace: Option<NameId>,
193        local_name: NameId,
194        resolved: Option<ElementKey>,
195    },
196}
197
198/// Inline model group definition
199#[derive(Debug, Clone)]
200pub struct ModelGroupDef {
201    /// Compositor type
202    pub compositor: Compositor,
203
204    /// Child particles
205    pub particles: Vec<ContentParticle>,
206
207    /// Source location
208    pub source: Option<SourceRef>,
209}
210
211/// Model group compositor
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub enum Compositor {
214    /// Sequence: all children in order
215    Sequence,
216    /// Choice: exactly one child
217    Choice,
218    /// All: all children in any order (XSD 1.0: top-level only, each once)
219    All,
220}
221
222/// Reference to a wildcard
223#[derive(Debug, Clone)]
224pub struct WildcardRef {
225    /// Namespace constraint
226    pub namespace_constraint: NamespaceConstraint,
227
228    /// Process contents mode
229    pub process_contents: ProcessContents,
230
231    /// Pre-expanded concrete QName exclusions (XSD 1.1 notQName)
232    pub not_qnames: Vec<(Option<NameId>, NameId)>,
233
234    /// XSD 1.1: notQName="##definedSibling" was specified but deferred
235    /// because sibling context was not yet available (open content wildcards).
236    /// Resolved later when attached to a content model.
237    pub has_defined_sibling: bool,
238
239    /// Source location
240    pub source: Option<SourceRef>,
241}
242
243/// Namespace constraint for wildcards
244#[derive(Debug, Clone, Default)]
245pub enum NamespaceConstraint {
246    /// Any namespace (##any)
247    #[default]
248    Any,
249    /// Other namespaces (##other)
250    Other,
251    /// Target namespace (##targetNamespace)
252    TargetNamespace,
253    /// Local elements only (##local)
254    Local,
255    /// Specific namespaces
256    List(Vec<Option<NameId>>),
257    /// XSD 1.1: Not these namespaces (notNamespace)
258    Not(Vec<Option<NameId>>),
259}
260
261impl NamespaceConstraint {
262    /// Check whether an element namespace matches this wildcard constraint.
263    pub fn matches(
264        &self,
265        element_namespace: Option<NameId>,
266        target_namespace: Option<NameId>,
267        xsd_version: XsdVersion,
268    ) -> bool {
269        match self {
270            NamespaceConstraint::Any => true,
271            NamespaceConstraint::Other => {
272                other_matches_namespace(element_namespace, target_namespace, xsd_version)
273            }
274            NamespaceConstraint::TargetNamespace => element_namespace == target_namespace,
275            NamespaceConstraint::Local => element_namespace.is_none(),
276            NamespaceConstraint::List(list) => list.contains(&element_namespace),
277            NamespaceConstraint::Not(excluded) => !excluded.contains(&element_namespace),
278        }
279    }
280}
281
282/// `##other` namespace predicate (§3.10.4, XSD 1.0 and 1.1 alike).
283///
284/// `##other` excludes the target namespace and the absent namespace. When the
285/// schema has no target namespace, it still excludes absent — only non-absent
286/// namespaces match.
287///
288/// `_xsd_version` is retained in the signature for API stability; the rule is
289/// version-independent after the XSD 1.1 §3.10.4 alignment.
290pub fn other_matches_namespace(
291    element_namespace: Option<NameId>,
292    target_namespace: Option<NameId>,
293    _xsd_version: XsdVersion,
294) -> bool {
295    element_namespace.is_some() && element_namespace != target_namespace
296}
297
298/// Check whether a (namespace, name) pair is excluded by a notQName list.
299pub fn not_qnames_exclude(
300    not_qnames: &[(Option<NameId>, NameId)],
301    namespace: Option<NameId>,
302    name: NameId,
303) -> bool {
304    not_qnames
305        .iter()
306        .any(|&(ns, n)| ns == namespace && n == name)
307}
308
309// Re-export ProcessContents from schema::wildcard to avoid duplication.
310pub use crate::schema::wildcard::ProcessContents;
311
312/// XSD 1.1: Open content specification
313///
314/// Runtime matching (interleave + suffix modes) is implemented in `validation/content.rs`.
315/// Schema-level validation stubs remain in `compiler/open_content.rs`.
316#[derive(Debug, Clone)]
317pub struct OpenContent {
318    /// Open content mode
319    pub mode: OpenContentMode,
320
321    /// Wildcard for open content
322    pub wildcard: Option<WildcardRef>,
323
324    /// Source location
325    pub source: Option<SourceRef>,
326}
327
328/// XSD 1.1: Open content mode
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
330pub enum OpenContentMode {
331    /// No open content
332    None,
333    /// Open content can appear interleaved
334    #[default]
335    Interleave,
336    /// Open content can appear at the end
337    Suffix,
338}
339
340impl From<crate::parser::frames::OpenContentMode> for OpenContentMode {
341    fn from(m: crate::parser::frames::OpenContentMode) -> Self {
342        use crate::parser::frames::OpenContentMode as Src;
343        match m {
344            Src::None => Self::None,
345            Src::Interleave => Self::Interleave,
346            Src::Suffix => Self::Suffix,
347        }
348    }
349}
350
351impl From<crate::schema::model::OpenContentMode> for OpenContentMode {
352    fn from(m: crate::schema::model::OpenContentMode) -> Self {
353        use crate::schema::model::OpenContentMode as Src;
354        match m {
355            Src::None => Self::None,
356            Src::Interleave => Self::Interleave,
357            Src::Suffix => Self::Suffix,
358        }
359    }
360}
361
362/// Attribute use (attribute declaration with use constraints)
363#[derive(Debug, Clone)]
364pub struct AttributeUse {
365    /// The attribute declaration
366    pub attribute: AttributeRef,
367
368    /// Use requirement
369    pub use_kind: AttributeUseKind,
370
371    /// Default value (mutually exclusive with fixed)
372    pub default_value: Option<String>,
373
374    /// Fixed value (mutually exclusive with default)
375    pub fixed_value: Option<String>,
376
377    /// Inheritable (XSD 1.1 §3.2.6, §3.3.5.6)
378    pub inheritable: bool,
379
380    /// Source location
381    pub source: Option<SourceRef>,
382}
383
384/// Attribute reference
385#[derive(Debug, Clone)]
386pub enum AttributeRef {
387    /// Local attribute declaration
388    Local(AttributeKey),
389    /// Reference to a global attribute
390    Global {
391        namespace: Option<NameId>,
392        local_name: NameId,
393        resolved: Option<AttributeKey>,
394    },
395}
396
397/// Attribute use requirement
398#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
399pub enum AttributeUseKind {
400    /// Attribute is required
401    Required,
402    /// Attribute is optional (default)
403    #[default]
404    Optional,
405    /// Attribute is prohibited
406    Prohibited,
407}
408
409/// Attribute wildcard (anyAttribute)
410#[derive(Debug, Clone)]
411pub struct AttributeWildcard {
412    /// Namespace constraint
413    pub namespace_constraint: NamespaceConstraint,
414
415    /// Process contents mode
416    pub process_contents: ProcessContents,
417
418    /// Source location
419    pub source: Option<SourceRef>,
420}
421
422/// Complex type definition
423///
424/// Represents an XSD complex type with content model and attributes.
425#[derive(Debug, Clone)]
426pub struct ComplexTypeDef {
427    /// Name (None for anonymous types)
428    pub name: Option<NameId>,
429
430    /// Target namespace
431    pub target_namespace: Option<NameId>,
432
433    /// Source location for error reporting
434    pub source: Option<SourceRef>,
435
436    /// Base type (from which this type is derived)
437    pub base_type: Option<TypeRef>,
438
439    /// Derivation method (restriction or extension)
440    pub derivation_method: Option<DerivationMethod>,
441
442    /// Content model
443    pub content: ComplexTypeContent,
444
445    /// Content kind (for quick access)
446    pub content_kind: ContentKind,
447
448    /// Attribute uses
449    pub attributes: Vec<AttributeUse>,
450
451    /// Attribute group references
452    pub attribute_groups: Vec<AttributeGroupKey>,
453
454    /// Attribute wildcard
455    pub attribute_wildcard: Option<AttributeWildcard>,
456
457    /// Final derivation control (which derivations are prohibited)
458    pub final_derivation: DerivationSet,
459
460    /// Block derivation control (which derivations are blocked for instances)
461    pub block: DerivationSet,
462
463    /// Abstract flag (cannot be used directly in instances)
464    pub is_abstract: bool,
465
466    /// Mixed content flag
467    pub mixed: bool,
468
469    /// ID attribute value
470    pub id: Option<String>,
471
472    /// XSD 1.1: Whether schema-level `defaultAttributes` group applies to this type.
473    /// Defaults to `true`; set to `false` via `defaultAttributesApply="false"`.
474    /// The resolver injects the schema-level attribute group into `resolved_attribute_groups`.
475    pub default_attributes_apply: bool,
476}
477
478impl ComplexTypeDef {
479    /// Create a new complex type with empty content
480    pub fn new(name: Option<NameId>, target_namespace: Option<NameId>) -> Self {
481        Self {
482            name,
483            target_namespace,
484            source: None,
485            base_type: None,
486            derivation_method: None,
487            content: ComplexTypeContent::Empty,
488            content_kind: ContentKind::Empty,
489            attributes: Vec::new(),
490            attribute_groups: Vec::new(),
491            attribute_wildcard: None,
492            final_derivation: DerivationSet::empty(),
493            block: DerivationSet::empty(),
494            is_abstract: false,
495            mixed: false,
496            id: None,
497            default_attributes_apply: true,
498        }
499    }
500
501    /// Check if this is an anonymous type
502    pub fn is_anonymous(&self) -> bool {
503        self.name.is_none()
504    }
505
506    /// Check if this is a global (named) type
507    pub fn is_global(&self) -> bool {
508        self.name.is_some()
509    }
510
511    /// Check if this type has simple content
512    pub fn has_simple_content(&self) -> bool {
513        matches!(self.content, ComplexTypeContent::Simple(_))
514    }
515
516    /// Check if this type has complex content
517    pub fn has_complex_content(&self) -> bool {
518        matches!(self.content, ComplexTypeContent::Complex(_))
519    }
520
521    /// Check if this type allows mixed content
522    pub fn allows_mixed(&self) -> bool {
523        self.mixed
524    }
525
526    /// Get the TypeKey for this complex type (requires its key)
527    pub fn type_key(&self, key: ComplexTypeKey) -> TypeKey {
528        TypeKey::Complex(key)
529    }
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535
536    #[test]
537    fn test_complex_type_creation() {
538        let ct = ComplexTypeDef::new(Some(NameId(1)), Some(NameId(2)));
539
540        assert!(ct.is_global());
541        assert!(!ct.is_anonymous());
542        assert!(!ct.is_abstract);
543        assert_eq!(ct.content_kind, ContentKind::Empty);
544    }
545
546    #[test]
547    fn test_anonymous_complex_type() {
548        let ct = ComplexTypeDef::new(None, None);
549        assert!(ct.is_anonymous());
550        assert!(!ct.is_global());
551    }
552
553    #[test]
554    fn test_content_particle_default() {
555        let particle = ContentParticle::new(ContentTerm::Group(ModelGroupDef {
556            compositor: Compositor::Sequence,
557            particles: vec![],
558            source: None,
559        }));
560
561        assert_eq!(particle.min_occurs, 1);
562        assert_eq!(particle.max_occurs, Some(1));
563        assert!(!particle.is_optional());
564        assert!(!particle.is_repeating());
565    }
566
567    #[test]
568    fn test_content_particle_unbounded() {
569        let particle = ContentParticle::with_occurs(
570            ContentTerm::Group(ModelGroupDef {
571                compositor: Compositor::Sequence,
572                particles: vec![],
573                source: None,
574            }),
575            0,
576            None,
577        );
578
579        assert!(particle.is_optional());
580        assert!(particle.is_repeating());
581        assert!(particle.is_unbounded());
582    }
583
584    #[test]
585    fn test_compositor_types() {
586        assert_eq!(Compositor::Sequence, Compositor::Sequence);
587        assert_ne!(Compositor::Sequence, Compositor::Choice);
588    }
589
590    #[test]
591    fn test_attribute_use_kind() {
592        assert_eq!(AttributeUseKind::default(), AttributeUseKind::Optional);
593    }
594
595    #[test]
596    fn test_process_contents() {
597        assert_eq!(ProcessContents::default(), ProcessContents::Strict);
598    }
599}