Skip to main content

xsd_schema/schema/
wildcard.rs

1//! Wildcard specifications
2//!
3//! This module defines wildcards for xs:any and xs:anyAttribute elements.
4
5use crate::ids::NameId;
6use crate::parser::location::SourceRef;
7use crate::schema::model::XsdVersion;
8
9/// Namespace constraint for wildcards
10///
11/// Specifies which namespaces are allowed by a wildcard.
12#[derive(Debug, Clone, PartialEq, Eq, Default)]
13pub enum NamespaceConstraint {
14    /// Any namespace allowed (##any)
15    #[default]
16    Any,
17
18    /// Other namespaces allowed (##other) - excludes target namespace
19    Other,
20
21    /// Specific set of namespaces
22    /// None in the set represents "no namespace" (##local)
23    Enumeration(Vec<Option<NameId>>),
24
25    /// Not these namespaces (XSD 1.1 notNamespace)
26    Not(Vec<Option<NameId>>),
27}
28
29impl NamespaceConstraint {
30    /// Create a constraint for "##any"
31    pub fn any() -> Self {
32        NamespaceConstraint::Any
33    }
34
35    /// Create a constraint for "##other"
36    pub fn other() -> Self {
37        NamespaceConstraint::Other
38    }
39
40    /// Create a constraint for "##targetNamespace"
41    pub fn target_namespace(ns: Option<NameId>) -> Self {
42        NamespaceConstraint::Enumeration(vec![ns])
43    }
44
45    /// Create a constraint for "##local"
46    pub fn local() -> Self {
47        NamespaceConstraint::Enumeration(vec![None])
48    }
49
50    /// Create a constraint from a list of namespaces
51    pub fn list(namespaces: Vec<Option<NameId>>) -> Self {
52        NamespaceConstraint::Enumeration(namespaces)
53    }
54
55    /// Check if this constraint allows a given namespace
56    pub fn allows(
57        &self,
58        ns: Option<NameId>,
59        target_ns: Option<NameId>,
60        xsd_version: XsdVersion,
61    ) -> bool {
62        match self {
63            NamespaceConstraint::Any => true,
64            NamespaceConstraint::Other => {
65                crate::types::complex::other_matches_namespace(ns, target_ns, xsd_version)
66            }
67            NamespaceConstraint::Enumeration(allowed) => allowed.contains(&ns),
68            NamespaceConstraint::Not(disallowed) => !disallowed.contains(&ns),
69        }
70    }
71}
72
73/// Process contents directive
74///
75/// Specifies how wildcard content should be validated.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
77pub enum ProcessContents {
78    /// Strictly validate - schema must be available, content must be valid
79    #[default]
80    Strict,
81
82    /// Laxly validate - validate if schema available, skip otherwise
83    Lax,
84
85    /// Skip validation entirely
86    Skip,
87}
88
89impl std::str::FromStr for ProcessContents {
90    type Err = ();
91
92    fn from_str(s: &str) -> Result<Self, Self::Err> {
93        match s {
94            "strict" => Ok(ProcessContents::Strict),
95            "lax" => Ok(ProcessContents::Lax),
96            "skip" => Ok(ProcessContents::Skip),
97            _ => Err(()),
98        }
99    }
100}
101
102impl ProcessContents {
103    /// Convert to string
104    pub fn as_str(&self) -> &'static str {
105        match self {
106            ProcessContents::Strict => "strict",
107            ProcessContents::Lax => "lax",
108            ProcessContents::Skip => "skip",
109        }
110    }
111}
112
113/// Element wildcard (xs:any)
114///
115/// Specifies a wildcard allowing any elements from specified namespaces.
116#[derive(Debug, Clone)]
117pub struct ElementWildcard {
118    /// Namespace constraint
119    pub namespace_constraint: NamespaceConstraint,
120
121    /// Process contents directive
122    pub process_contents: ProcessContents,
123
124    /// Minimum occurrences
125    pub min_occurs: u32,
126
127    /// Maximum occurrences (None = unbounded)
128    pub max_occurs: Option<u32>,
129
130    /// Source location for error reporting
131    pub source: Option<SourceRef>,
132
133    /// ID attribute value
134    pub id: Option<String>,
135
136    /// XSD 1.1: notQName exclusions (populated by compiler, checked at validation)
137    pub not_qnames: Vec<QNameDisallowed>,
138}
139
140impl ElementWildcard {
141    /// Create a new element wildcard
142    pub fn new() -> Self {
143        Self {
144            namespace_constraint: NamespaceConstraint::Any,
145            process_contents: ProcessContents::Strict,
146            min_occurs: 1,
147            max_occurs: Some(1),
148            source: None,
149            id: None,
150            not_qnames: Vec::new(),
151        }
152    }
153
154    /// Create a wildcard with ##any namespace and lax processing
155    pub fn any_lax() -> Self {
156        Self {
157            namespace_constraint: NamespaceConstraint::Any,
158            process_contents: ProcessContents::Lax,
159            min_occurs: 0,
160            max_occurs: None,
161            source: None,
162            id: None,
163            not_qnames: Vec::new(),
164        }
165    }
166
167    /// Check if this wildcard is optional
168    pub fn is_optional(&self) -> bool {
169        self.min_occurs == 0
170    }
171
172    /// Check if this wildcard is unbounded
173    pub fn is_unbounded(&self) -> bool {
174        self.max_occurs.is_none()
175    }
176}
177
178impl Default for ElementWildcard {
179    fn default() -> Self {
180        Self::new()
181    }
182}
183
184/// Attribute wildcard (xs:anyAttribute)
185///
186/// Specifies a wildcard allowing any attributes from specified namespaces.
187#[derive(Debug, Clone)]
188pub struct AttributeWildcard {
189    /// Namespace constraint
190    pub namespace_constraint: NamespaceConstraint,
191
192    /// Process contents directive
193    pub process_contents: ProcessContents,
194
195    /// Source location for error reporting
196    pub source: Option<SourceRef>,
197
198    /// ID attribute value
199    pub id: Option<String>,
200
201    /// XSD 1.1: notQName exclusions (populated by compiler, checked at validation)
202    pub not_qnames: Vec<QNameDisallowed>,
203}
204
205impl AttributeWildcard {
206    /// Create a new attribute wildcard
207    pub fn new() -> Self {
208        Self {
209            namespace_constraint: NamespaceConstraint::Any,
210            process_contents: ProcessContents::Strict,
211            source: None,
212            id: None,
213            not_qnames: Vec::new(),
214        }
215    }
216
217    /// Create a wildcard with ##any namespace and lax processing
218    pub fn any_lax() -> Self {
219        Self {
220            namespace_constraint: NamespaceConstraint::Any,
221            process_contents: ProcessContents::Lax,
222            source: None,
223            id: None,
224            not_qnames: Vec::new(),
225        }
226    }
227}
228
229impl Default for AttributeWildcard {
230    fn default() -> Self {
231        Self::new()
232    }
233}
234
235/// XSD 1.1: Disallowed QName for notQName constraint
236#[derive(Debug, Clone)]
237pub enum QNameDisallowed {
238    /// Specific QName that is disallowed
239    QName {
240        namespace: Option<NameId>,
241        local_name: NameId,
242    },
243    /// ##defined - disallow elements defined in schema
244    Defined,
245    /// ##definedSibling - disallow sibling elements
246    DefinedSibling,
247}
248
249// The stubbed `WildcardUnion::union` / `::intersection` were removed — the
250// canonical §3.10.6.3 cos-aw-union lives in `schema::derivation::wildcard_result_union`
251// (added by B1, extended by B3). No production code depended on the stubs.
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn test_namespace_constraint_any() {
259        let constraint = NamespaceConstraint::any();
260        assert!(constraint.allows(Some(NameId(1)), None, XsdVersion::V1_0));
261        assert!(constraint.allows(None, None, XsdVersion::V1_0));
262    }
263
264    #[test]
265    fn test_namespace_constraint_other() {
266        let constraint = NamespaceConstraint::other();
267        let target = Some(NameId(1));
268
269        assert!(!constraint.allows(target, target, XsdVersion::V1_0)); // Same as target - not allowed
270        assert!(constraint.allows(Some(NameId(2)), target, XsdVersion::V1_0)); // Different - allowed
271                                                                               // XSD 1.0 and 1.1: absent namespace is NOT allowed by ##other
272                                                                               // (§3.10.4: when the schema has a target namespace, ##other excludes
273                                                                               // both the target ns and the absent ns).
274        assert!(!constraint.allows(None, target, XsdVersion::V1_0));
275        assert!(!constraint.allows(None, target, XsdVersion::V1_1));
276    }
277
278    #[test]
279    fn test_namespace_constraint_enumeration() {
280        let constraint = NamespaceConstraint::list(vec![Some(NameId(1)), Some(NameId(2))]);
281
282        assert!(constraint.allows(Some(NameId(1)), None, XsdVersion::V1_0));
283        assert!(constraint.allows(Some(NameId(2)), None, XsdVersion::V1_0));
284        assert!(!constraint.allows(Some(NameId(3)), None, XsdVersion::V1_0));
285    }
286
287    #[test]
288    fn test_process_contents_parsing() {
289        assert_eq!("strict".parse(), Ok(ProcessContents::Strict));
290        assert_eq!("lax".parse(), Ok(ProcessContents::Lax));
291        assert_eq!("skip".parse(), Ok(ProcessContents::Skip));
292        assert_eq!("invalid".parse::<ProcessContents>(), Err(()));
293    }
294
295    #[test]
296    fn test_element_wildcard_default() {
297        let wildcard = ElementWildcard::new();
298        assert_eq!(wildcard.process_contents, ProcessContents::Strict);
299        assert_eq!(wildcard.min_occurs, 1);
300        assert_eq!(wildcard.max_occurs, Some(1));
301    }
302
303    #[test]
304    fn test_element_wildcard_any_lax() {
305        let wildcard = ElementWildcard::any_lax();
306        assert!(wildcard.is_optional());
307        assert!(wildcard.is_unbounded());
308        assert_eq!(wildcard.process_contents, ProcessContents::Lax);
309    }
310
311    #[test]
312    fn test_attribute_wildcard_default() {
313        let wildcard = AttributeWildcard::new();
314        assert_eq!(wildcard.process_contents, ProcessContents::Strict);
315    }
316}