use super::table::{well_known, NameTable};
use crate::ids::NameId;
use std::collections::HashMap;
pub struct NamespaceContext<'a> {
name_table: &'a mut NameTable,
scopes: Vec<HashMap<NameId, NameId>>,
default_namespace: Option<NameId>,
default_ns_stack: Vec<Option<NameId>>,
}
impl<'a> NamespaceContext<'a> {
pub fn new(name_table: &'a mut NameTable) -> Self {
let mut ctx = Self {
name_table,
scopes: Vec::new(),
default_namespace: None,
default_ns_stack: Vec::new(),
};
ctx.push_scope();
ctx.scopes[0].insert(well_known::XML_PREFIX, well_known::XML_NAMESPACE);
ctx.scopes[0].insert(well_known::XMLNS_PREFIX, well_known::XMLNS_NAMESPACE);
ctx
}
pub fn name_table(&self) -> &NameTable {
self.name_table
}
pub fn name_table_mut(&mut self) -> &mut NameTable {
self.name_table
}
pub fn push_scope(&mut self) {
self.scopes.push(HashMap::new());
self.default_ns_stack.push(self.default_namespace);
}
pub fn pop_scope(&mut self) {
self.scopes.pop().expect("No scope to pop");
self.default_namespace = self.default_ns_stack.pop().flatten();
}
pub fn add_namespace(&mut self, prefix: &str, uri: &str) {
let uri_id = self.name_table.add(uri);
if prefix.is_empty() {
self.default_namespace = if uri.is_empty() {
None } else {
Some(uri_id)
};
} else {
let prefix_id = self.name_table.add(prefix);
if let Some(scope) = self.scopes.last_mut() {
scope.insert(prefix_id, uri_id);
}
}
}
pub fn lookup_namespace(&self, prefix: &str) -> Option<NameId> {
if let Some(prefix_id) = self.name_table.get(prefix) {
self.lookup_namespace_by_id(prefix_id)
} else {
None
}
}
pub fn lookup_namespace_by_id(&self, prefix_id: NameId) -> Option<NameId> {
for scope in self.scopes.iter().rev() {
if let Some(&ns_id) = scope.get(&prefix_id) {
return Some(ns_id);
}
}
None
}
pub fn default_namespace(&self) -> Option<NameId> {
self.default_namespace
}
pub fn set_default_namespace(&mut self, uri: Option<&str>) {
self.default_namespace = uri.map(|u| self.name_table.add(u));
}
pub fn set_default_namespace_id(&mut self, ns: Option<NameId>) {
self.default_namespace = ns;
}
pub fn get_namespaces_in_scope(&self, scope_filter: NamespaceScope) -> Vec<(NameId, NameId)> {
let mut result = HashMap::new();
for scope in &self.scopes {
for (&prefix_id, &ns_id) in scope {
result.insert(prefix_id, ns_id);
}
}
result
.into_iter()
.filter(|&(prefix_id, _)| match scope_filter {
NamespaceScope::All => true,
NamespaceScope::ExcludeXml => {
prefix_id != well_known::XML_PREFIX && prefix_id != well_known::XMLNS_PREFIX
}
})
.collect()
}
pub fn depth(&self) -> usize {
self.scopes.len()
}
pub fn snapshot(&self) -> NamespaceContextSnapshot {
NamespaceContextSnapshot {
default_ns: self.default_namespace,
bindings: self.get_namespaces_in_scope(NamespaceScope::ExcludeXml),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NamespaceScope {
All,
ExcludeXml,
}
#[derive(Debug, Clone, Default)]
pub struct NamespaceContextSnapshot {
pub default_ns: Option<NameId>,
pub bindings: Vec<(NameId, NameId)>,
}
impl NamespaceContextSnapshot {
pub fn resolve_prefix(&self, prefix_id: NameId) -> Option<NameId> {
for &(p, ns) in &self.bindings {
if p == prefix_id {
return Some(ns);
}
}
if prefix_id == well_known::XML_PREFIX {
return Some(well_known::XML_NAMESPACE);
}
None
}
pub fn default_namespace(&self) -> Option<NameId> {
self.default_ns
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_push_pop_scope() {
let mut table = NameTable::new();
let mut ctx = NamespaceContext::new(&mut table);
let initial_depth = ctx.depth();
ctx.push_scope();
assert_eq!(ctx.depth(), initial_depth + 1);
ctx.pop_scope();
assert_eq!(ctx.depth(), initial_depth);
}
#[test]
fn test_add_and_lookup_namespace() {
let mut table = NameTable::new();
let mut ctx = NamespaceContext::new(&mut table);
ctx.push_scope();
ctx.add_namespace("foo", "http://example.com/foo");
let ns = ctx.lookup_namespace("foo");
assert!(ns.is_some());
let ns_str = ctx.name_table().resolve(ns.unwrap());
assert_eq!(ns_str, "http://example.com/foo");
}
#[test]
fn test_scope_shadowing() {
let mut table = NameTable::new();
let mut ctx = NamespaceContext::new(&mut table);
ctx.push_scope();
ctx.add_namespace("foo", "http://outer.com");
ctx.push_scope();
ctx.add_namespace("foo", "http://inner.com");
let ns = ctx.lookup_namespace("foo").unwrap();
assert_eq!(ctx.name_table().resolve(ns), "http://inner.com");
ctx.pop_scope();
let ns = ctx.lookup_namespace("foo").unwrap();
assert_eq!(ctx.name_table().resolve(ns), "http://outer.com");
}
#[test]
fn test_default_namespace() {
let mut table = NameTable::new();
let mut ctx = NamespaceContext::new(&mut table);
assert!(ctx.default_namespace().is_none());
ctx.push_scope();
ctx.add_namespace("", "http://default.com");
assert!(ctx.default_namespace().is_some());
ctx.pop_scope();
assert!(ctx.default_namespace().is_none());
}
#[test]
fn test_undeclare_default_namespace() {
let mut table = NameTable::new();
let mut ctx = NamespaceContext::new(&mut table);
ctx.push_scope();
ctx.add_namespace("", "http://default.com");
assert!(ctx.default_namespace().is_some());
ctx.push_scope();
ctx.add_namespace("", ""); assert!(ctx.default_namespace().is_none());
}
#[test]
fn test_xml_prefix_always_bound() {
let mut table = NameTable::new();
let ctx = NamespaceContext::new(&mut table);
let ns = ctx.lookup_namespace("xml");
assert!(ns.is_some());
assert_eq!(
ctx.name_table().resolve(ns.unwrap()),
super::super::table::XML_NAMESPACE
);
}
#[test]
fn test_snapshot() {
let mut table = NameTable::new();
let mut ctx = NamespaceContext::new(&mut table);
ctx.push_scope();
ctx.add_namespace("foo", "http://foo.com");
ctx.add_namespace("", "http://default.com");
let snapshot = ctx.snapshot();
assert!(snapshot.default_ns.is_some());
assert!(!snapshot.bindings.is_empty());
}
}