Skip to main content

xsd_schema/namespace/
context.rs

1//! Namespace context with scoped prefix mappings
2//!
3//! Provides hierarchical namespace management for XML parsing.
4//! Each element can push a new scope, and scopes are popped when elements close.
5
6use super::table::{well_known, NameTable};
7use crate::ids::NameId;
8use std::collections::HashMap;
9
10/// Scoped prefix-to-namespace mapping
11///
12/// Maintains a stack of scopes for namespace resolution during parsing.
13/// Each scope can add new prefix bindings that shadow outer scopes.
14///
15/// # Example
16///
17/// ```
18/// use xsd_schema::namespace::{NameTable, NamespaceContext};
19///
20/// let mut table = NameTable::new();
21/// let mut ctx = NamespaceContext::new(&mut table);
22///
23/// // Root scope with XSD namespace
24/// ctx.push_scope();
25/// ctx.add_namespace("xs", "http://www.w3.org/2001/XMLSchema");
26///
27/// // Inner scope can shadow
28/// ctx.push_scope();
29/// ctx.add_namespace("xs", "http://different/namespace");
30///
31/// ctx.pop_scope(); // Back to original binding
32/// ctx.pop_scope(); // Root scope removed
33/// ```
34pub struct NamespaceContext<'a> {
35    /// Reference to the name table for string interning
36    name_table: &'a mut NameTable,
37    /// Stack of scopes, each mapping prefix NameId -> namespace NameId
38    scopes: Vec<HashMap<NameId, NameId>>,
39    /// Default namespace (unprefixed elements)
40    default_namespace: Option<NameId>,
41    /// Stack of default namespace values (for scoped changes)
42    default_ns_stack: Vec<Option<NameId>>,
43}
44
45impl<'a> NamespaceContext<'a> {
46    /// Create a new namespace context with standard bindings
47    ///
48    /// Pre-binds:
49    /// - xml -> http://www.w3.org/XML/1998/namespace
50    /// - xmlns -> http://www.w3.org/2000/xmlns/
51    pub fn new(name_table: &'a mut NameTable) -> Self {
52        let mut ctx = Self {
53            name_table,
54            scopes: Vec::new(),
55            default_namespace: None,
56            default_ns_stack: Vec::new(),
57        };
58
59        // Start with root scope containing standard bindings
60        ctx.push_scope();
61
62        // Bind xml and xmlns prefixes (these are always in scope per XML spec)
63        ctx.scopes[0].insert(well_known::XML_PREFIX, well_known::XML_NAMESPACE);
64        ctx.scopes[0].insert(well_known::XMLNS_PREFIX, well_known::XMLNS_NAMESPACE);
65
66        ctx
67    }
68
69    /// Get a reference to the name table
70    pub fn name_table(&self) -> &NameTable {
71        self.name_table
72    }
73
74    /// Get a mutable reference to the name table
75    pub fn name_table_mut(&mut self) -> &mut NameTable {
76        self.name_table
77    }
78
79    /// Push a new scope (called on element start)
80    pub fn push_scope(&mut self) {
81        self.scopes.push(HashMap::new());
82        self.default_ns_stack.push(self.default_namespace);
83    }
84
85    /// Pop the current scope (called on element end)
86    ///
87    /// # Panics
88    ///
89    /// Panics if there are no scopes to pop.
90    pub fn pop_scope(&mut self) {
91        self.scopes.pop().expect("No scope to pop");
92        self.default_namespace = self.default_ns_stack.pop().flatten();
93    }
94
95    /// Add a namespace binding to the current scope
96    ///
97    /// # Arguments
98    ///
99    /// * `prefix` - The prefix (empty string for default namespace)
100    /// * `uri` - The namespace URI
101    pub fn add_namespace(&mut self, prefix: &str, uri: &str) {
102        let uri_id = self.name_table.add(uri);
103
104        if prefix.is_empty() {
105            // Default namespace
106            self.default_namespace = if uri.is_empty() {
107                None // xmlns="" undeclares default namespace
108            } else {
109                Some(uri_id)
110            };
111        } else {
112            let prefix_id = self.name_table.add(prefix);
113            if let Some(scope) = self.scopes.last_mut() {
114                scope.insert(prefix_id, uri_id);
115            }
116        }
117    }
118
119    /// Look up namespace URI for a prefix string
120    pub fn lookup_namespace(&self, prefix: &str) -> Option<NameId> {
121        if let Some(prefix_id) = self.name_table.get(prefix) {
122            self.lookup_namespace_by_id(prefix_id)
123        } else {
124            None
125        }
126    }
127
128    /// Look up namespace URI for a prefix NameId
129    pub fn lookup_namespace_by_id(&self, prefix_id: NameId) -> Option<NameId> {
130        // Search scopes from innermost to outermost
131        for scope in self.scopes.iter().rev() {
132            if let Some(&ns_id) = scope.get(&prefix_id) {
133                return Some(ns_id);
134            }
135        }
136        None
137    }
138
139    /// Get the default namespace (for unprefixed elements)
140    pub fn default_namespace(&self) -> Option<NameId> {
141        self.default_namespace
142    }
143
144    /// Set the default namespace directly
145    pub fn set_default_namespace(&mut self, uri: Option<&str>) {
146        self.default_namespace = uri.map(|u| self.name_table.add(u));
147    }
148
149    /// Set the default namespace to an already-interned `NameId`.
150    ///
151    /// Used by the parser to install a chameleon-adopted namespace on the
152    /// root `<xs:schema>` scope (ยง4.2.3 clause 2.3): when an included schema
153    /// has no `targetNamespace` and no explicit `xmlns` default, unqualified
154    /// QName references inside it must resolve to the includer's target
155    /// namespace. Pre-setting the scope's default namespace produces that
156    /// resolution naturally during subsequent QName parsing.
157    pub fn set_default_namespace_id(&mut self, ns: Option<NameId>) {
158        self.default_namespace = ns;
159    }
160
161    /// Get all namespace bindings in scope
162    ///
163    /// # Arguments
164    ///
165    /// * `scope_filter` - Filter for which namespaces to include
166    ///
167    /// Returns Vec of (prefix_id, namespace_id) pairs.
168    pub fn get_namespaces_in_scope(&self, scope_filter: NamespaceScope) -> Vec<(NameId, NameId)> {
169        let mut result = HashMap::new();
170
171        // Collect all bindings (inner scopes override outer)
172        for scope in &self.scopes {
173            for (&prefix_id, &ns_id) in scope {
174                result.insert(prefix_id, ns_id);
175            }
176        }
177
178        // Filter based on scope type
179        result
180            .into_iter()
181            .filter(|&(prefix_id, _)| match scope_filter {
182                NamespaceScope::All => true,
183                NamespaceScope::ExcludeXml => {
184                    prefix_id != well_known::XML_PREFIX && prefix_id != well_known::XMLNS_PREFIX
185                }
186            })
187            .collect()
188    }
189
190    /// Get current scope depth
191    pub fn depth(&self) -> usize {
192        self.scopes.len()
193    }
194
195    /// Create a snapshot of current namespace bindings
196    pub fn snapshot(&self) -> NamespaceContextSnapshot {
197        NamespaceContextSnapshot {
198            default_ns: self.default_namespace,
199            bindings: self.get_namespaces_in_scope(NamespaceScope::ExcludeXml),
200        }
201    }
202}
203
204/// Filter for get_namespaces_in_scope
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub enum NamespaceScope {
207    /// Include all namespace bindings
208    All,
209    /// Exclude xml and xmlns bindings
210    ExcludeXml,
211}
212
213/// Snapshot of namespace bindings at a point in time
214///
215/// Used to capture context for annotation processing and QName resolution.
216#[derive(Debug, Clone, Default)]
217pub struct NamespaceContextSnapshot {
218    /// Default namespace at snapshot time
219    pub default_ns: Option<NameId>,
220    /// All prefix bindings (excluding xml/xmlns)
221    pub bindings: Vec<(NameId, NameId)>,
222}
223
224impl NamespaceContextSnapshot {
225    /// Look up namespace URI for a prefix NameId
226    ///
227    /// Searches through the captured bindings to find the namespace
228    /// associated with the given prefix.
229    pub fn resolve_prefix(&self, prefix_id: NameId) -> Option<NameId> {
230        for &(p, ns) in &self.bindings {
231            if p == prefix_id {
232                return Some(ns);
233            }
234        }
235        // Check well-known xml prefix (always in scope per XML spec)
236        if prefix_id == well_known::XML_PREFIX {
237            return Some(well_known::XML_NAMESPACE);
238        }
239        None
240    }
241
242    /// Get the default namespace (for unprefixed elements)
243    pub fn default_namespace(&self) -> Option<NameId> {
244        self.default_ns
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    #[test]
253    fn test_push_pop_scope() {
254        let mut table = NameTable::new();
255        let mut ctx = NamespaceContext::new(&mut table);
256
257        let initial_depth = ctx.depth();
258        ctx.push_scope();
259        assert_eq!(ctx.depth(), initial_depth + 1);
260        ctx.pop_scope();
261        assert_eq!(ctx.depth(), initial_depth);
262    }
263
264    #[test]
265    fn test_add_and_lookup_namespace() {
266        let mut table = NameTable::new();
267        let mut ctx = NamespaceContext::new(&mut table);
268
269        ctx.push_scope();
270        ctx.add_namespace("foo", "http://example.com/foo");
271
272        let ns = ctx.lookup_namespace("foo");
273        assert!(ns.is_some());
274
275        // Access name_table through ctx to avoid borrow conflict
276        let ns_str = ctx.name_table().resolve(ns.unwrap());
277        assert_eq!(ns_str, "http://example.com/foo");
278    }
279
280    #[test]
281    fn test_scope_shadowing() {
282        let mut table = NameTable::new();
283        let mut ctx = NamespaceContext::new(&mut table);
284
285        ctx.push_scope();
286        ctx.add_namespace("foo", "http://outer.com");
287
288        ctx.push_scope();
289        ctx.add_namespace("foo", "http://inner.com");
290
291        // Inner scope shadows outer
292        let ns = ctx.lookup_namespace("foo").unwrap();
293        assert_eq!(ctx.name_table().resolve(ns), "http://inner.com");
294
295        ctx.pop_scope();
296
297        // Back to outer binding
298        let ns = ctx.lookup_namespace("foo").unwrap();
299        assert_eq!(ctx.name_table().resolve(ns), "http://outer.com");
300    }
301
302    #[test]
303    fn test_default_namespace() {
304        let mut table = NameTable::new();
305        let mut ctx = NamespaceContext::new(&mut table);
306
307        assert!(ctx.default_namespace().is_none());
308
309        ctx.push_scope();
310        ctx.add_namespace("", "http://default.com");
311        assert!(ctx.default_namespace().is_some());
312
313        ctx.pop_scope();
314        assert!(ctx.default_namespace().is_none());
315    }
316
317    #[test]
318    fn test_undeclare_default_namespace() {
319        let mut table = NameTable::new();
320        let mut ctx = NamespaceContext::new(&mut table);
321
322        ctx.push_scope();
323        ctx.add_namespace("", "http://default.com");
324        assert!(ctx.default_namespace().is_some());
325
326        ctx.push_scope();
327        ctx.add_namespace("", ""); // Undeclare
328        assert!(ctx.default_namespace().is_none());
329    }
330
331    #[test]
332    fn test_xml_prefix_always_bound() {
333        let mut table = NameTable::new();
334        let ctx = NamespaceContext::new(&mut table);
335
336        let ns = ctx.lookup_namespace("xml");
337        assert!(ns.is_some());
338        assert_eq!(
339            ctx.name_table().resolve(ns.unwrap()),
340            super::super::table::XML_NAMESPACE
341        );
342    }
343
344    #[test]
345    fn test_snapshot() {
346        let mut table = NameTable::new();
347        let mut ctx = NamespaceContext::new(&mut table);
348
349        ctx.push_scope();
350        ctx.add_namespace("foo", "http://foo.com");
351        ctx.add_namespace("", "http://default.com");
352
353        let snapshot = ctx.snapshot();
354        assert!(snapshot.default_ns.is_some());
355        assert!(!snapshot.bindings.is_empty());
356    }
357}