Skip to main content

xsd_schema/schema/
group.rs

1//! Model groups and attribute groups
2//!
3//! This module defines named model groups (xs:group) and attribute groups (xs:attributeGroup).
4
5use crate::ids::{AttributeGroupKey, ModelGroupKey, NameId};
6use crate::parser::location::SourceRef;
7use crate::types::complex::{AttributeUse, AttributeWildcard, Compositor, ContentParticle};
8
9/// Named model group definition (xs:group)
10///
11/// Represents a reusable content model that can be referenced by complex types.
12#[derive(Debug, Clone)]
13pub struct ModelGroupDef {
14    /// Group name (required for global groups)
15    pub name: Option<NameId>,
16
17    /// Target namespace
18    pub target_namespace: Option<NameId>,
19
20    /// Source location for error reporting
21    pub source: Option<SourceRef>,
22
23    /// The compositor (sequence, choice, or all)
24    pub compositor: Compositor,
25
26    /// Child particles
27    pub particles: Vec<ContentParticle>,
28
29    /// ID attribute value
30    pub id: Option<String>,
31}
32
33impl ModelGroupDef {
34    /// Create a new named model group
35    pub fn new(name: NameId, compositor: Compositor) -> Self {
36        Self {
37            name: Some(name),
38            target_namespace: None,
39            source: None,
40            compositor,
41            particles: Vec::new(),
42            id: None,
43        }
44    }
45
46    /// Create a new anonymous model group
47    pub fn anonymous(compositor: Compositor) -> Self {
48        Self {
49            name: None,
50            target_namespace: None,
51            source: None,
52            compositor,
53            particles: Vec::new(),
54            id: None,
55        }
56    }
57
58    /// Check if this is a named (global) group
59    pub fn is_named(&self) -> bool {
60        self.name.is_some()
61    }
62
63    /// Check if this group is empty
64    pub fn is_empty(&self) -> bool {
65        self.particles.is_empty()
66    }
67
68    /// Add a particle to this group
69    pub fn add_particle(&mut self, particle: ContentParticle) {
70        self.particles.push(particle);
71    }
72}
73
74/// Reference to a model group
75#[derive(Debug, Clone)]
76pub enum ModelGroupRef {
77    /// Resolved reference to a named group
78    Resolved(ModelGroupKey),
79    /// Unresolved reference
80    Unresolved {
81        namespace: Option<NameId>,
82        local_name: NameId,
83    },
84}
85
86/// Attribute group definition (xs:attributeGroup)
87///
88/// Represents a reusable collection of attribute declarations and wildcards.
89#[derive(Debug, Clone)]
90pub struct AttributeGroupDef {
91    /// Group name (required for global groups)
92    pub name: Option<NameId>,
93
94    /// Target namespace
95    pub target_namespace: Option<NameId>,
96
97    /// Source location for error reporting
98    pub source: Option<SourceRef>,
99
100    /// Attribute uses in this group
101    pub attributes: Vec<AttributeUse>,
102
103    /// Attribute group references
104    pub attribute_group_refs: Vec<AttributeGroupRef>,
105
106    /// Attribute wildcard (anyAttribute)
107    pub attribute_wildcard: Option<AttributeWildcard>,
108
109    /// ID attribute value
110    pub id: Option<String>,
111}
112
113impl AttributeGroupDef {
114    /// Create a new named attribute group
115    pub fn new(name: NameId) -> Self {
116        Self {
117            name: Some(name),
118            target_namespace: None,
119            source: None,
120            attributes: Vec::new(),
121            attribute_group_refs: Vec::new(),
122            attribute_wildcard: None,
123            id: None,
124        }
125    }
126
127    /// Create a new anonymous attribute group
128    pub fn anonymous() -> Self {
129        Self {
130            name: None,
131            target_namespace: None,
132            source: None,
133            attributes: Vec::new(),
134            attribute_group_refs: Vec::new(),
135            attribute_wildcard: None,
136            id: None,
137        }
138    }
139
140    /// Check if this is a named (global) group
141    pub fn is_named(&self) -> bool {
142        self.name.is_some()
143    }
144
145    /// Check if this group is empty
146    pub fn is_empty(&self) -> bool {
147        self.attributes.is_empty()
148            && self.attribute_group_refs.is_empty()
149            && self.attribute_wildcard.is_none()
150    }
151
152    /// Add an attribute use to this group
153    pub fn add_attribute(&mut self, attr_use: AttributeUse) {
154        self.attributes.push(attr_use);
155    }
156
157    /// Add an attribute group reference
158    pub fn add_attribute_group_ref(&mut self, ref_: AttributeGroupRef) {
159        self.attribute_group_refs.push(ref_);
160    }
161}
162
163/// Reference to an attribute group
164#[derive(Debug, Clone)]
165pub enum AttributeGroupRef {
166    /// Resolved reference
167    Resolved(AttributeGroupKey),
168    /// Unresolved reference
169    Unresolved {
170        namespace: Option<NameId>,
171        local_name: NameId,
172    },
173}
174
175/// Particle occurrence constraints
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub struct Occurrence {
178    /// Minimum occurrences
179    pub min: u32,
180    /// Maximum occurrences (None = unbounded)
181    pub max: Option<u32>,
182}
183
184impl Occurrence {
185    /// Default occurrence (1..1)
186    pub const ONCE: Occurrence = Occurrence {
187        min: 1,
188        max: Some(1),
189    };
190
191    /// Optional occurrence (0..1)
192    pub const OPTIONAL: Occurrence = Occurrence {
193        min: 0,
194        max: Some(1),
195    };
196
197    /// Unbounded occurrence (0..unbounded)
198    pub const UNBOUNDED: Occurrence = Occurrence { min: 0, max: None };
199
200    /// Required with unbounded max (1..unbounded)
201    pub const ONE_OR_MORE: Occurrence = Occurrence { min: 1, max: None };
202
203    /// Create a custom occurrence
204    pub fn new(min: u32, max: Option<u32>) -> Self {
205        Self { min, max }
206    }
207
208    /// Check if this occurrence is optional
209    pub fn is_optional(&self) -> bool {
210        self.min == 0
211    }
212
213    /// Check if this occurrence is unbounded
214    pub fn is_unbounded(&self) -> bool {
215        self.max.is_none()
216    }
217
218    /// Check if this occurrence allows multiple
219    pub fn allows_multiple(&self) -> bool {
220        self.max.is_none_or(|m| m > 1)
221    }
222
223    /// Check if this occurrence is exactly once
224    pub fn is_once(&self) -> bool {
225        self.min == 1 && self.max == Some(1)
226    }
227}
228
229impl Default for Occurrence {
230    fn default() -> Self {
231        Self::ONCE
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn test_model_group_def() {
241        let group = ModelGroupDef::new(NameId(1), Compositor::Sequence);
242        assert!(group.is_named());
243        assert!(group.is_empty());
244        assert_eq!(group.compositor, Compositor::Sequence);
245    }
246
247    #[test]
248    fn test_anonymous_model_group() {
249        let group = ModelGroupDef::anonymous(Compositor::Choice);
250        assert!(!group.is_named());
251        assert_eq!(group.compositor, Compositor::Choice);
252    }
253
254    #[test]
255    fn test_attribute_group_def() {
256        let group = AttributeGroupDef::new(NameId(1));
257        assert!(group.is_named());
258        assert!(group.is_empty());
259    }
260
261    #[test]
262    fn test_anonymous_attribute_group() {
263        let group = AttributeGroupDef::anonymous();
264        assert!(!group.is_named());
265        assert!(group.is_empty());
266    }
267
268    #[test]
269    fn test_occurrence_constants() {
270        assert!(Occurrence::ONCE.is_once());
271        assert!(!Occurrence::ONCE.is_optional());
272        assert!(!Occurrence::ONCE.is_unbounded());
273
274        assert!(Occurrence::OPTIONAL.is_optional());
275        assert!(!Occurrence::OPTIONAL.allows_multiple());
276
277        assert!(Occurrence::UNBOUNDED.is_unbounded());
278        assert!(Occurrence::UNBOUNDED.allows_multiple());
279
280        assert!(!Occurrence::ONE_OR_MORE.is_optional());
281        assert!(Occurrence::ONE_OR_MORE.is_unbounded());
282    }
283
284    #[test]
285    fn test_occurrence_custom() {
286        let occ = Occurrence::new(2, Some(5));
287        assert!(!occ.is_optional());
288        assert!(!occ.is_unbounded());
289        assert!(occ.allows_multiple());
290    }
291}