Skip to main content

xsd_schema/parser/frames/
groups.rs

1// ============================================================================
2// Model Group Frame (sequence, choice, all)
3// ============================================================================
4
5/// Frame for xs:sequence, xs:choice, xs:all
6pub struct ModelGroupFrame {
7    compositor: Compositor,
8    min_occurs: u32,
9    max_occurs: Option<u32>,
10    id: Option<String>,
11    particles: Vec<ParticleResult>,
12    /// True once any non-annotation child has been seen (annotation must come first)
13    past_annotation: bool,
14    annotation: Option<Annotation>,
15    source: Option<SourceRef>,
16    foreign_attributes: Vec<ForeignAttribute>,
17}
18
19impl ModelGroupFrame {
20    pub fn new(
21        compositor: Compositor,
22        attrs: &AttributeMap,
23        name_table: &NameTable,
24        source: Option<SourceRef>,
25        _ns_snapshot: &NamespaceContextSnapshot,
26    ) -> SchemaResult<Self> {
27        let min_occurs = parse_min_occurs_attr(attrs, name_table, "minOccurs")?;
28
29        let max_occurs = parse_max_occurs_attr(attrs, name_table, "maxOccurs")?;
30
31        let id = attrs
32            .get_value_by_name(name_table, "id")
33            .map(String::from);
34
35        Ok(Self {
36            compositor,
37            min_occurs,
38            max_occurs,
39            id,
40            particles: Vec::new(),
41            past_annotation: false,
42            annotation: None,
43            source,
44            foreign_attributes: Vec::new(),
45        })
46    }
47}
48
49impl Frame for ModelGroupFrame {
50    fn allows(&self, local_name: &str, _name_table: &NameTable) -> bool {
51        // Annotation must come first
52        if local_name == xsd_names::ANNOTATION && self.past_annotation {
53            return false;
54        }
55        match self.compositor {
56            Compositor::All => matches!(
57                local_name,
58                xsd_names::ANNOTATION | xsd_names::ELEMENT | xsd_names::ANY | xsd_names::GROUP
59            ),
60            Compositor::Sequence | Compositor::Choice => matches!(
61                local_name,
62                xsd_names::ANNOTATION
63                    | xsd_names::ELEMENT
64                    | xsd_names::GROUP
65                    | xsd_names::SEQUENCE
66                    | xsd_names::CHOICE
67                    | xsd_names::ANY
68            ),
69        }
70    }
71
72    fn allows_attribute(&self, local_name: &str, _name_table: &NameTable) -> bool {
73        matches!(local_name, "minOccurs" | "maxOccurs" | "id")
74    }
75
76    fn on_child_start(&mut self, local_name: &str, _name_table: &NameTable) {
77        if local_name != xsd_names::ANNOTATION {
78            self.past_annotation = true;
79        }
80    }
81
82    fn attach(&mut self, child: FrameResult) -> SchemaResult<()> {
83        match child {
84            FrameResult::Annotation(ann) => {
85                self.annotation = Some(ann);
86            }
87            FrameResult::Element(elem) => {
88                let min_occurs = elem.min_occurs;
89                let max_occurs = elem.max_occurs;
90                let source = elem.source.clone();
91                self.particles.push(ParticleResult {
92                    term: ParticleTerm::Element(elem),
93                    min_occurs,
94                    max_occurs,
95                    source,
96                });
97            }
98            FrameResult::Particle(particle) => {
99                self.particles.push(particle);
100            }
101            FrameResult::Wildcard(wc) => {
102                let source = wc.source.clone();
103                self.particles.push(ParticleResult {
104                    term: ParticleTerm::Any(wc),
105                    min_occurs: 1,
106                    max_occurs: Some(1),
107                    source,
108                });
109            }
110            FrameResult::Group(GroupFrameResult::Model(mg)) => {
111                let min_occurs = mg.min_occurs;
112                let max_occurs = mg.max_occurs;
113                let source = mg.source.clone();
114                self.particles.push(ParticleResult {
115                    term: ParticleTerm::Group(*mg),
116                    min_occurs,
117                    max_occurs,
118                    source,
119                });
120            }
121            FrameResult::Skip => {}
122            _ => {}
123        }
124        Ok(())
125    }
126
127    fn finish(self: Box<Self>) -> SchemaResult<FrameResult> {
128        let annotation = merge_foreign_attributes(
129            self.annotation,
130            self.foreign_attributes,
131            self.source.clone(),
132        );
133        Ok(FrameResult::Particle(ParticleResult {
134            term: ParticleTerm::Group(ModelGroupDefResult {
135                name: None,
136                ref_name: None,
137                compositor: Some(self.compositor),
138                particles: self.particles,
139                min_occurs: self.min_occurs,
140                max_occurs: self.max_occurs,
141                id: self.id,
142                annotation,
143                source: self.source.clone(),
144            }),
145            min_occurs: self.min_occurs,
146            max_occurs: self.max_occurs,
147            source: self.source,
148        }))
149    }
150
151    fn has_annotation(&self) -> bool {
152        self.annotation.is_some()
153    }
154
155    fn source(&self) -> Option<&SourceRef> {
156        self.source.as_ref()
157    }
158
159    fn set_foreign_attributes(&mut self, attrs: Vec<ForeignAttribute>) {
160        self.foreign_attributes = attrs;
161    }
162}
163
164// ============================================================================
165// Group Frame (named model group)
166// ============================================================================
167
168/// Frame for xs:group (named or reference)
169pub struct GroupFrame {
170    name: Option<NameId>,
171    ref_name: Option<QNameRef>,
172    min_occurs: u32,
173    max_occurs: Option<u32>,
174    id: Option<String>,
175    compositor: Option<Compositor>,
176    particles: Vec<ParticleResult>,
177    /// True once any non-annotation child has been seen (annotation must come first)
178    past_annotation: bool,
179    annotation: Option<Annotation>,
180    source: Option<SourceRef>,
181    foreign_attributes: Vec<ForeignAttribute>,
182}
183
184impl GroupFrame {
185    pub fn new(
186        attrs: &AttributeMap,
187        name_table: &NameTable,
188        source: Option<SourceRef>,
189        ns_snapshot: &NamespaceContextSnapshot,
190    ) -> SchemaResult<Self> {
191        let name = attrs
192            .get_value_by_name(name_table, "name")
193            .and_then(|s| name_table.get(s));
194
195        let ref_name = attrs
196            .get_value_by_name(name_table, "ref")
197            .map(|s| parse_qname_ref(s, name_table, ns_snapshot))
198            .transpose()?;
199
200        let min_occurs = parse_min_occurs_attr(attrs, name_table, "minOccurs")?;
201
202        let max_occurs = parse_max_occurs_attr(attrs, name_table, "maxOccurs")?;
203
204        let id = attrs
205            .get_value_by_name(name_table, "id")
206            .map(String::from);
207
208        Ok(Self {
209            name,
210            ref_name,
211            min_occurs,
212            max_occurs,
213            id,
214            compositor: None,
215            particles: Vec::new(),
216            past_annotation: false,
217            annotation: None,
218            source,
219            foreign_attributes: Vec::new(),
220        })
221    }
222}
223
224impl Frame for GroupFrame {
225    fn allows(&self, local_name: &str, _name_table: &NameTable) -> bool {
226        // Annotation must come first (before compositor)
227        if local_name == xsd_names::ANNOTATION && self.past_annotation {
228            return false;
229        }
230        // xs:group ref-form content model: (annotation?). The compositor is
231        // only allowed in the named (top-level) form.
232        if self.ref_name.is_some() {
233            return local_name == xsd_names::ANNOTATION;
234        }
235        // xs:group content model: (annotation?, (all | choice | sequence)?) — at most one compositor
236        if self.compositor.is_some()
237            && matches!(
238                local_name,
239                xsd_names::SEQUENCE | xsd_names::CHOICE | xsd_names::ALL
240            )
241        {
242            return false;
243        }
244        matches!(
245            local_name,
246            xsd_names::ANNOTATION | xsd_names::SEQUENCE | xsd_names::CHOICE | xsd_names::ALL
247        )
248    }
249
250    fn allows_attribute(&self, local_name: &str, _name_table: &NameTable) -> bool {
251        matches!(
252            local_name,
253            "name" | "ref" | "minOccurs" | "maxOccurs" | "id"
254        )
255    }
256
257    fn on_child_start(&mut self, local_name: &str, _name_table: &NameTable) {
258        if local_name != xsd_names::ANNOTATION {
259            self.past_annotation = true;
260        }
261    }
262
263    fn attach(&mut self, child: FrameResult) -> SchemaResult<()> {
264        match child {
265            FrameResult::Annotation(ann) => {
266                self.annotation = Some(ann);
267            }
268            FrameResult::Particle(ParticleResult {
269                term: ParticleTerm::Group(mg),
270                min_occurs,
271                max_occurs,
272                ..
273            }) => {
274                // For named (top-level) group definitions, the compositor child
275                // must not have minOccurs/maxOccurs (cos-all-limited / src-group)
276                // Safety net: reject if a compositor was already attached
277                if self.compositor.is_some() {
278                    return Err(SchemaError::structural(
279                        "src-group",
280                        "Group definition must have exactly one compositor child (sequence, choice, or all)",
281                        None,
282                    ));
283                }
284                // For named (top-level) group definitions, the compositor child
285                // must not have minOccurs/maxOccurs (cos-all-limited / src-group)
286                if self.name.is_some() && (min_occurs != 1 || max_occurs != Some(1)) {
287                    return Err(SchemaError::structural(
288                        "src-group",
289                        "Compositor in top-level group definition cannot have minOccurs/maxOccurs",
290                        None,
291                    ));
292                }
293                self.compositor = mg.compositor;
294                self.particles = mg.particles;
295            }
296            FrameResult::Skip => {}
297            _ => {}
298        }
299        Ok(())
300    }
301
302    fn finish(self: Box<Self>) -> SchemaResult<FrameResult> {
303        // Named group definitions must have exactly one compositor child
304        if self.name.is_some() && self.ref_name.is_none() && self.compositor.is_none() {
305            return Err(SchemaError::structural(
306                "src-group",
307                "Named group definition must contain a compositor (sequence, choice, or all)",
308                None,
309            ));
310        }
311        let annotation = merge_foreign_attributes(
312            self.annotation,
313            self.foreign_attributes,
314            self.source.clone(),
315        );
316        Ok(FrameResult::Group(GroupFrameResult::Model(Box::new(ModelGroupDefResult {
317            name: self.name,
318            ref_name: self.ref_name,
319            compositor: self.compositor,
320            particles: self.particles,
321            min_occurs: self.min_occurs,
322            max_occurs: self.max_occurs,
323            id: self.id,
324            annotation,
325            source: self.source,
326        }))))
327    }
328
329    fn has_annotation(&self) -> bool {
330        self.annotation.is_some()
331    }
332
333    fn source(&self) -> Option<&SourceRef> {
334        self.source.as_ref()
335    }
336
337    fn set_foreign_attributes(&mut self, attrs: Vec<ForeignAttribute>) {
338        self.foreign_attributes = attrs;
339    }
340}
341
342// ============================================================================
343// Attribute Group Frame
344// ============================================================================
345
346/// Frame for xs:attributeGroup
347pub struct AttributeGroupFrame {
348    name: Option<NameId>,
349    ref_name: Option<QNameRef>,
350    id: Option<String>,
351    attributes: Vec<AttributeUseResult>,
352    attribute_groups: Vec<QNameRef>,
353    attribute_wildcard: Option<WildcardResult>,
354    /// True once any non-annotation child has been seen (annotation must come first)
355    past_annotation: bool,
356    annotation: Option<Annotation>,
357    source: Option<SourceRef>,
358    foreign_attributes: Vec<ForeignAttribute>,
359}
360
361impl AttributeGroupFrame {
362    pub fn new(
363        attrs: &AttributeMap,
364        name_table: &NameTable,
365        source: Option<SourceRef>,
366        ns_snapshot: &NamespaceContextSnapshot,
367    ) -> SchemaResult<Self> {
368        let name = attrs
369            .get_value_by_name(name_table, "name")
370            .and_then(|s| name_table.get(s));
371
372        let ref_name = attrs
373            .get_value_by_name(name_table, "ref")
374            .map(|s| parse_qname_ref(s, name_table, ns_snapshot))
375            .transpose()?;
376
377        let id = attrs
378            .get_value_by_name(name_table, "id")
379            .map(String::from);
380
381        Ok(Self {
382            name,
383            ref_name,
384            id,
385            attributes: Vec::new(),
386            attribute_groups: Vec::new(),
387            attribute_wildcard: None,
388            past_annotation: false,
389            annotation: None,
390            source,
391            foreign_attributes: Vec::new(),
392        })
393    }
394}
395
396impl Frame for AttributeGroupFrame {
397    fn allows(&self, local_name: &str, _name_table: &NameTable) -> bool {
398        // Annotation must come first
399        if local_name == xsd_names::ANNOTATION && self.past_annotation {
400            return false;
401        }
402        // When `ref` is present, only annotation is allowed (ref-form has no
403        // attribute or wildcard children — XML Representation of <attributeGroup>).
404        if self.ref_name.is_some() {
405            return local_name == xsd_names::ANNOTATION;
406        }
407        matches!(
408            local_name,
409            xsd_names::ANNOTATION
410                | xsd_names::ATTRIBUTE
411                | xsd_names::ATTRIBUTE_GROUP
412                | xsd_names::ANY_ATTRIBUTE
413        )
414    }
415
416    fn allows_attribute(&self, local_name: &str, _name_table: &NameTable) -> bool {
417        matches!(local_name, "name" | "ref" | "id")
418    }
419
420    fn on_child_start(&mut self, local_name: &str, _name_table: &NameTable) {
421        if local_name != xsd_names::ANNOTATION {
422            self.past_annotation = true;
423        }
424    }
425
426    fn attach(&mut self, child: FrameResult) -> SchemaResult<()> {
427        match child {
428            FrameResult::Annotation(ann) => {
429                self.annotation = Some(ann);
430            }
431            FrameResult::Attribute(attr) => {
432                let use_kind = match attr.use_kind.as_deref() {
433                    Some("required") => AttributeUseKind::Required,
434                    Some("prohibited") => AttributeUseKind::Prohibited,
435                    _ => AttributeUseKind::Optional,
436                };
437                self.attributes.push(AttributeUseResult {
438                    attribute: attr,
439                    use_kind,
440                });
441            }
442            FrameResult::Group(GroupFrameResult::Attribute(ag)) => {
443                if let Some(ref_name) = ag.ref_name {
444                    self.attribute_groups.push(ref_name);
445                }
446            }
447            FrameResult::Wildcard(wc) => {
448                self.attribute_wildcard = Some(wc);
449            }
450            FrameResult::Skip => {}
451            _ => {}
452        }
453        Ok(())
454    }
455
456    fn finish(self: Box<Self>) -> SchemaResult<FrameResult> {
457        let annotation = merge_foreign_attributes(
458            self.annotation,
459            self.foreign_attributes,
460            self.source.clone(),
461        );
462        Ok(FrameResult::Group(GroupFrameResult::Attribute(Box::new(AttributeGroupDefResult {
463            name: self.name,
464            ref_name: self.ref_name,
465            attributes: self.attributes,
466            attribute_groups: self.attribute_groups,
467            attribute_wildcard: self.attribute_wildcard,
468            id: self.id,
469            annotation,
470            source: self.source,
471        }))))
472    }
473
474    fn has_annotation(&self) -> bool {
475        self.annotation.is_some()
476    }
477
478    fn source(&self) -> Option<&SourceRef> {
479        self.source.as_ref()
480    }
481
482    fn set_foreign_attributes(&mut self, attrs: Vec<ForeignAttribute>) {
483        self.foreign_attributes = attrs;
484    }
485}
486