Skip to main content

xsd_schema/parser/frames/
identity.rs

1// ============================================================================
2// Identity Constraint Frames
3// ============================================================================
4
5/// Frame for xs:selector
6pub struct SelectorFrame {
7    xpath: String,
8    xpath_default_namespace: Option<String>,
9    ns_snapshot: NamespaceContextSnapshot,
10    id: Option<String>,
11    annotation: Option<Annotation>,
12    source: Option<SourceRef>,
13    foreign_attributes: Vec<ForeignAttribute>,
14}
15
16impl SelectorFrame {
17    pub fn new(
18        attrs: &AttributeMap,
19        name_table: &NameTable,
20        source: Option<SourceRef>,
21        ns_snapshot: NamespaceContextSnapshot,
22    ) -> SchemaResult<Self> {
23        let xpath = attrs
24            .get_value_by_name(name_table, "xpath")
25            .map(String::from)
26            .unwrap_or_default();
27
28        #[cfg(feature = "xsd11")]
29        let xpath_default_namespace = attrs
30            .get_value_by_name(name_table, "xpathDefaultNamespace")
31            .map(String::from);
32        #[cfg(not(feature = "xsd11"))]
33        let xpath_default_namespace: Option<String> = None;
34
35        let id = attrs
36            .get_value_by_name(name_table, "id")
37            .map(String::from);
38
39        Ok(Self {
40            xpath,
41            xpath_default_namespace,
42            ns_snapshot,
43            id,
44            annotation: None,
45            source,
46            foreign_attributes: Vec::new(),
47        })
48    }
49}
50
51impl Frame for SelectorFrame {
52    fn allows(&self, local_name: &str, _name_table: &NameTable) -> bool {
53        matches!(local_name, xsd_names::ANNOTATION)
54    }
55
56    fn allows_attribute(&self, local_name: &str, _name_table: &NameTable) -> bool {
57        #[cfg(feature = "xsd11")]
58        if local_name == "xpathDefaultNamespace" {
59            return true;
60        }
61        matches!(local_name, "xpath" | "id")
62    }
63
64    fn on_child_start(&mut self, _local_name: &str, _name_table: &NameTable) {}
65
66    fn attach(&mut self, child: FrameResult) -> SchemaResult<()> {
67        if let FrameResult::Annotation(ann) = child {
68            self.annotation = Some(ann);
69        }
70        Ok(())
71    }
72
73    fn finish(self: Box<Self>) -> SchemaResult<FrameResult> {
74        let annotation = merge_foreign_attributes(
75            self.annotation,
76            self.foreign_attributes,
77            self.source.clone(),
78        );
79        Ok(FrameResult::Selector(SelectorResult {
80            xpath: self.xpath,
81            xpath_default_namespace: self.xpath_default_namespace,
82            ns_snapshot: self.ns_snapshot,
83            id: self.id,
84            annotation,
85            source: self.source,
86        }))
87    }
88
89    fn has_annotation(&self) -> bool {
90        self.annotation.is_some()
91    }
92
93    fn source(&self) -> Option<&SourceRef> {
94        self.source.as_ref()
95    }
96
97    fn set_foreign_attributes(&mut self, attrs: Vec<ForeignAttribute>) {
98        self.foreign_attributes = attrs;
99    }
100}
101
102/// Frame for xs:field
103pub struct FieldFrame {
104    xpath: String,
105    xpath_default_namespace: Option<String>,
106    ns_snapshot: NamespaceContextSnapshot,
107    id: Option<String>,
108    annotation: Option<Annotation>,
109    source: Option<SourceRef>,
110    foreign_attributes: Vec<ForeignAttribute>,
111}
112
113impl FieldFrame {
114    pub fn new(
115        attrs: &AttributeMap,
116        name_table: &NameTable,
117        source: Option<SourceRef>,
118        ns_snapshot: NamespaceContextSnapshot,
119    ) -> SchemaResult<Self> {
120        let xpath = attrs
121            .get_value_by_name(name_table, "xpath")
122            .map(String::from)
123            .unwrap_or_default();
124
125        #[cfg(feature = "xsd11")]
126        let xpath_default_namespace = attrs
127            .get_value_by_name(name_table, "xpathDefaultNamespace")
128            .map(String::from);
129        #[cfg(not(feature = "xsd11"))]
130        let xpath_default_namespace: Option<String> = None;
131
132        let id = attrs
133            .get_value_by_name(name_table, "id")
134            .map(String::from);
135
136        Ok(Self {
137            xpath,
138            xpath_default_namespace,
139            ns_snapshot,
140            id,
141            annotation: None,
142            source,
143            foreign_attributes: Vec::new(),
144        })
145    }
146}
147
148impl Frame for FieldFrame {
149    fn allows(&self, local_name: &str, _name_table: &NameTable) -> bool {
150        matches!(local_name, xsd_names::ANNOTATION)
151    }
152
153    fn allows_attribute(&self, local_name: &str, _name_table: &NameTable) -> bool {
154        #[cfg(feature = "xsd11")]
155        if local_name == "xpathDefaultNamespace" {
156            return true;
157        }
158        matches!(local_name, "xpath" | "id")
159    }
160
161    fn on_child_start(&mut self, _local_name: &str, _name_table: &NameTable) {}
162
163    fn attach(&mut self, child: FrameResult) -> SchemaResult<()> {
164        if let FrameResult::Annotation(ann) = child {
165            self.annotation = Some(ann);
166        }
167        Ok(())
168    }
169
170    fn finish(self: Box<Self>) -> SchemaResult<FrameResult> {
171        let annotation = merge_foreign_attributes(
172            self.annotation,
173            self.foreign_attributes,
174            self.source.clone(),
175        );
176        Ok(FrameResult::Field(FieldResult {
177            xpath: self.xpath,
178            xpath_default_namespace: self.xpath_default_namespace,
179            ns_snapshot: self.ns_snapshot,
180            id: self.id,
181            annotation,
182            source: self.source,
183        }))
184    }
185
186    fn has_annotation(&self) -> bool {
187        self.annotation.is_some()
188    }
189
190    fn source(&self) -> Option<&SourceRef> {
191        self.source.as_ref()
192    }
193
194    fn set_foreign_attributes(&mut self, attrs: Vec<ForeignAttribute>) {
195        self.foreign_attributes = attrs;
196    }
197}
198
199/// Frame for xs:key, xs:keyref, xs:unique
200pub struct IdentityFrame {
201    kind: IdentityKind,
202    name: Option<NameId>,
203    raw_name: Option<String>,
204    ref_name: Option<QNameRef>,
205    refer: Option<QNameRef>,
206    id: Option<String>,
207    selector: Option<SelectorResult>,
208    fields: Vec<FieldResult>,
209    annotation: Option<Annotation>,
210    source: Option<SourceRef>,
211    foreign_attributes: Vec<ForeignAttribute>,
212}
213
214impl IdentityFrame {
215    pub fn new(
216        kind: IdentityKind,
217        attrs: &AttributeMap,
218        name_table: &NameTable,
219        source: Option<SourceRef>,
220        ns_snapshot: &NamespaceContextSnapshot,
221    ) -> SchemaResult<Self> {
222        let raw_name = attrs
223            .get_value_by_name(name_table, "name")
224            .map(String::from);
225        let name = raw_name.as_deref()
226            .and_then(|s| name_table.get(s));
227
228        let ref_name = attrs
229            .get_value_by_name(name_table, "ref")
230            .map(|s| parse_qname_ref(s, name_table, ns_snapshot))
231            .transpose()?;
232
233        let refer = if kind == IdentityKind::Keyref {
234            attrs
235                .get_value_by_name(name_table, "refer")
236                .map(|s| parse_qname_ref(s, name_table, ns_snapshot))
237                .transpose()?
238        } else {
239            None
240        };
241
242        let id = attrs
243            .get_value_by_name(name_table, "id")
244            .map(String::from);
245
246        Ok(Self {
247            kind,
248            name,
249            raw_name,
250            ref_name,
251            refer,
252            id,
253            selector: None,
254            fields: Vec::new(),
255            annotation: None,
256            source,
257            foreign_attributes: Vec::new(),
258        })
259    }
260}
261
262impl Frame for IdentityFrame {
263    fn allows(&self, local_name: &str, _name_table: &NameTable) -> bool {
264        // §3.11.6 clause 4: if @ref is present, only annotation is allowed
265        if self.ref_name.is_some() {
266            return local_name == xsd_names::ANNOTATION
267                && !self.has_annotation();
268        }
269        // Content model: (annotation?, selector, field+)
270        if local_name == xsd_names::ANNOTATION {
271            // Annotation must come before selector/field
272            return self.selector.is_none() && self.fields.is_empty() && !self.has_annotation();
273        }
274        if local_name == xsd_names::SELECTOR {
275            // Exactly one selector, must come before any field
276            return self.selector.is_none() && self.fields.is_empty();
277        }
278        if local_name == xsd_names::FIELD {
279            // Fields must come after selector
280            return self.selector.is_some();
281        }
282        false
283    }
284
285    fn allows_attribute(&self, local_name: &str, _name_table: &NameTable) -> bool {
286        if local_name == "refer" {
287            // Only keyref has a 'refer' attribute, and §3.11.6 clause 4
288            // prohibits 'refer' when @ref is present
289            return self.kind == IdentityKind::Keyref && self.ref_name.is_none();
290        }
291        matches!(local_name, "name" | "ref" | "id")
292    }
293
294    fn on_child_start(&mut self, _local_name: &str, _name_table: &NameTable) {}
295
296    fn attach(&mut self, child: FrameResult) -> SchemaResult<()> {
297        match child {
298            FrameResult::Annotation(ann) => {
299                self.annotation = Some(ann);
300            }
301            FrameResult::Selector(selector) => {
302                self.selector = Some(selector);
303            }
304            FrameResult::Field(field) => {
305                self.fields.push(field);
306            }
307            FrameResult::Skip => {}
308            _ => {}
309        }
310        Ok(())
311    }
312
313    fn finish(self: Box<Self>) -> SchemaResult<FrameResult> {
314        let annotation = merge_foreign_attributes(
315            self.annotation,
316            self.foreign_attributes,
317            self.source.clone(),
318        );
319
320        // XSD 1.1: @ref means this is a reference, not a definition (§3.11.2)
321        if let Some(ref_name) = self.ref_name {
322            // §3.11.6 clause 1: @name and @ref are mutually exclusive
323            if self.name.is_some() {
324                return Err(SchemaError::structural(
325                    "src-identity-constraint.1",
326                    "Identity constraint with 'ref' must not have 'name'",
327                    None,
328                ));
329            }
330            return Ok(FrameResult::IdentityRef(IdentityRefResult {
331                kind: self.kind,
332                ref_name,
333                id: self.id,
334                annotation,
335                source: self.source,
336            }));
337        }
338
339        // Non-ref path: validate name, selector, and fields
340        let name = match (&self.name, &self.raw_name) {
341            (Some(id), Some(raw)) if is_ncname(raw) => *id,
342            (_, Some(raw)) => {
343                return Err(SchemaError::structural(
344                    "src-identity-constraint",
345                    format!("Identity constraint 'name' attribute value '{}' is not a valid NCName", raw),
346                    None,
347                ));
348            }
349            _ => {
350                return Err(SchemaError::structural(
351                    "src-identity-constraint",
352                    "Identity constraint requires 'name' attribute",
353                    None,
354                ));
355            }
356        };
357
358        let selector = self.selector.ok_or_else(|| {
359            SchemaError::structural(
360                "src-identity-constraint",
361                "Identity constraint requires a selector",
362                None,
363            )
364        })?;
365
366        if self.fields.is_empty() {
367            return Err(SchemaError::structural(
368                "src-identity-constraint",
369                "Identity constraint requires at least one field",
370                None,
371            ));
372        }
373
374        Ok(FrameResult::Identity(IdentityResult {
375            kind: self.kind,
376            name,
377            ref_name: None,
378            refer: self.refer,
379            selector,
380            fields: self.fields,
381            id: self.id,
382            annotation,
383            source: self.source,
384        }))
385    }
386
387    fn has_annotation(&self) -> bool {
388        self.annotation.is_some()
389    }
390
391    fn source(&self) -> Option<&SourceRef> {
392        self.source.as_ref()
393    }
394
395    fn set_foreign_attributes(&mut self, attrs: Vec<ForeignAttribute>) {
396        self.foreign_attributes = attrs;
397    }
398}
399