Skip to main content

xsd_schema/parser/frames/
open_content.rs

1// ============================================================================
2// Open Content Frames (XSD 1.1)
3// ============================================================================
4
5/// Reject `minOccurs` / `maxOccurs` on the wildcard child of `xs:openContent`
6/// or `xs:defaultOpenContent`.
7///
8/// The XSD 1.1 schema for schemas declares these two parents as containing
9/// a restricted wildcard type with `minOccurs` and `maxOccurs` **prohibited**
10/// (W3C Bugzilla 15618; saxon open048 test). When the parser sees default
11/// values `(1, Some(1))` we assume the attributes were absent; any other
12/// value means the schema author explicitly wrote a disallowed attribute.
13fn validate_open_content_wildcard_occurs(
14    min_occurs: u32,
15    max_occurs: Option<u32>,
16    _source: Option<&SourceRef>,
17) -> SchemaResult<()> {
18    if min_occurs != 1 || max_occurs != Some(1) {
19        return Err(SchemaError::structural(
20            "src-openContent",
21            "xs:any inside xs:openContent/xs:defaultOpenContent must not \
22             carry 'minOccurs' or 'maxOccurs' (XSD 1.1 structures schema, \
23             see W3C Bugzilla 15618)"
24                .to_string(),
25            None,
26        ));
27    }
28    Ok(())
29}
30
31/// Frame for xs:openContent
32pub struct OpenContentFrame {
33    mode: OpenContentMode,
34    wildcard: Option<WildcardResult>,
35    id: Option<String>,
36    annotation: Option<Annotation>,
37    source: Option<SourceRef>,
38    foreign_attributes: Vec<ForeignAttribute>,
39}
40
41impl OpenContentFrame {
42    pub fn new(
43        attrs: &AttributeMap,
44        name_table: &NameTable,
45        source: Option<SourceRef>,
46    ) -> SchemaResult<Self> {
47        let mode = parse_open_content_mode_attr(attrs, name_table, "mode")?;
48
49        let id = attrs
50            .get_value_by_name(name_table, "id")
51            .map(String::from);
52
53        Ok(Self {
54            mode,
55            wildcard: None,
56            id,
57            annotation: None,
58            source,
59            foreign_attributes: Vec::new(),
60        })
61    }
62}
63
64impl Frame for OpenContentFrame {
65    fn allows(&self, local_name: &str, _name_table: &NameTable) -> bool {
66        matches!(local_name, xsd_names::ANNOTATION | xsd_names::ANY)
67    }
68
69    fn allows_attribute(&self, local_name: &str, _name_table: &NameTable) -> bool {
70        matches!(local_name, "id" | "mode")
71    }
72
73    fn on_child_start(&mut self, _local_name: &str, _name_table: &NameTable) {}
74
75    fn attach(&mut self, child: FrameResult) -> SchemaResult<()> {
76        match child {
77            FrameResult::Annotation(ann) => {
78                self.annotation = Some(ann);
79            }
80            FrameResult::Particle(particle) => {
81                if let ParticleTerm::Any(wc) = particle.term {
82                    // XSD 1.1 §3.4.2 (schema for schemas): the xs:any child of
83                    // xs:openContent uses a restricted wildcard type that
84                    // PROHIBITS minOccurs and maxOccurs. See W3C Bugzilla
85                    // 15618 and the saxon open048 test. Reject any wildcard
86                    // whose occurrence range differs from the default [1,1].
87                    validate_open_content_wildcard_occurs(
88                        particle.min_occurs,
89                        particle.max_occurs,
90                        particle.source.as_ref(),
91                    )?;
92                    self.wildcard = Some(wc);
93                }
94            }
95            FrameResult::Skip => {}
96            _ => {}
97        }
98        Ok(())
99    }
100
101    fn finish(self: Box<Self>) -> SchemaResult<FrameResult> {
102        // §3.4.2 / W3C Bugzilla 7069: when xs:openContent declares mode="none"
103        // the wildcard child is meaningless and the schema for schemas
104        // disallows it. Report the schema as structurally invalid.
105        if self.mode == OpenContentMode::None && self.wildcard.is_some() {
106            return Err(SchemaError::structural(
107                "src-openContent",
108                "xs:openContent with mode='none' must not contain an xs:any \
109                 child wildcard (W3C Bugzilla 7069)"
110                    .to_string(),
111                None,
112            ));
113        }
114        let annotation = merge_foreign_attributes(
115            self.annotation,
116            self.foreign_attributes,
117            self.source.clone(),
118        );
119        Ok(FrameResult::OpenContent(OpenContentResult {
120            mode: self.mode,
121            wildcard: self.wildcard,
122            id: self.id,
123            annotation,
124            source: self.source,
125        }))
126    }
127
128    fn has_annotation(&self) -> bool {
129        self.annotation.is_some()
130    }
131
132    fn source(&self) -> Option<&SourceRef> {
133        self.source.as_ref()
134    }
135
136    fn set_foreign_attributes(&mut self, attrs: Vec<ForeignAttribute>) {
137        self.foreign_attributes = attrs;
138    }
139}
140
141/// Frame for xs:defaultOpenContent
142pub struct DefaultOpenContentFrame {
143    mode: OpenContentMode,
144    applies_to_empty: bool,
145    wildcard: Option<WildcardResult>,
146    id: Option<String>,
147    annotation: Option<Annotation>,
148    source: Option<SourceRef>,
149    foreign_attributes: Vec<ForeignAttribute>,
150}
151
152impl DefaultOpenContentFrame {
153    pub fn new(
154        attrs: &AttributeMap,
155        name_table: &NameTable,
156        source: Option<SourceRef>,
157    ) -> SchemaResult<Self> {
158        let mode = parse_open_content_mode_attr(attrs, name_table, "mode")?;
159
160        let applies_to_empty =
161            parse_bool_attr_default(attrs, name_table, "appliesToEmpty", false)?;
162
163        let id = attrs
164            .get_value_by_name(name_table, "id")
165            .map(String::from);
166
167        Ok(Self {
168            mode,
169            applies_to_empty,
170            wildcard: None,
171            id,
172            annotation: None,
173            source,
174            foreign_attributes: Vec::new(),
175        })
176    }
177}
178
179impl Frame for DefaultOpenContentFrame {
180    fn allows(&self, local_name: &str, _name_table: &NameTable) -> bool {
181        matches!(local_name, xsd_names::ANNOTATION | xsd_names::ANY)
182    }
183
184    fn allows_attribute(&self, local_name: &str, _name_table: &NameTable) -> bool {
185        matches!(local_name, "id" | "mode" | "appliesToEmpty")
186    }
187
188    fn on_child_start(&mut self, _local_name: &str, _name_table: &NameTable) {}
189
190    fn attach(&mut self, child: FrameResult) -> SchemaResult<()> {
191        match child {
192            FrameResult::Annotation(ann) => {
193                self.annotation = Some(ann);
194            }
195            FrameResult::Particle(particle) => {
196                if let ParticleTerm::Any(wc) = particle.term {
197                    // Same restriction applies to xs:defaultOpenContent — the
198                    // wildcard child prohibits minOccurs / maxOccurs.
199                    validate_open_content_wildcard_occurs(
200                        particle.min_occurs,
201                        particle.max_occurs,
202                        particle.source.as_ref(),
203                    )?;
204                    self.wildcard = Some(wc);
205                }
206            }
207            FrameResult::Skip => {}
208            _ => {}
209        }
210        Ok(())
211    }
212
213    fn finish(self: Box<Self>) -> SchemaResult<FrameResult> {
214        let annotation = merge_foreign_attributes(
215            self.annotation,
216            self.foreign_attributes,
217            self.source.clone(),
218        );
219        Ok(FrameResult::DefaultOpenContent(DefaultOpenContentResult {
220            mode: self.mode,
221            applies_to_empty: self.applies_to_empty,
222            wildcard: self.wildcard,
223            id: self.id,
224            annotation,
225            source: self.source,
226        }))
227    }
228
229    fn has_annotation(&self) -> bool {
230        self.annotation.is_some()
231    }
232
233    fn source(&self) -> Option<&SourceRef> {
234        self.source.as_ref()
235    }
236
237    fn set_foreign_attributes(&mut self, attrs: Vec<ForeignAttribute>) {
238        self.foreign_attributes = attrs;
239    }
240}
241