use std::borrow::Cow;
use crate::dom::{Document, NodeId, NodeKind};
pub const XML_NAMESPACE: &str = "http://www.w3.org/XML/1998/namespace";
pub const XMLNS_NAMESPACE: &str = "http://www.w3.org/2000/xmlns/";
#[derive(Debug, Clone)]
struct NamespaceScope<'a> {
bindings: Vec<(Cow<'a, str>, Cow<'a, str>)>,
}
impl<'a> NamespaceScope<'a> {
fn new() -> Self {
NamespaceScope {
bindings: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct NamespaceResolver<'a> {
scopes: Vec<NamespaceScope<'a>>,
}
impl<'a> NamespaceResolver<'a> {
pub fn new() -> Self {
let mut root_scope = NamespaceScope::new();
root_scope
.bindings
.push((Cow::Borrowed("xml"), Cow::Borrowed(XML_NAMESPACE)));
root_scope
.bindings
.push((Cow::Borrowed("xmlns"), Cow::Borrowed(XMLNS_NAMESPACE)));
NamespaceResolver {
scopes: vec![root_scope],
}
}
pub fn push_scope(&mut self) {
self.scopes.push(NamespaceScope::new());
}
pub fn pop_scope(&mut self) {
if self.scopes.len() > 1 {
self.scopes.pop();
}
}
pub fn declare(&mut self, prefix: Cow<'a, str>, uri: Cow<'a, str>) {
if &*prefix == "xmlns" {
return;
}
if &*prefix == "xml" && &*uri != XML_NAMESPACE {
return;
}
if &*uri == XMLNS_NAMESPACE {
return;
}
if let Some(scope) = self.scopes.last_mut() {
scope.bindings.push((prefix, uri));
}
}
pub fn resolve(&self, prefix: &str) -> Option<&Cow<'a, str>> {
for scope in self.scopes.iter().rev() {
for (p, uri) in scope.bindings.iter().rev() {
if **p == *prefix {
return Some(uri);
}
}
}
None
}
pub fn resolve_default(&self) -> Option<&Cow<'a, str>> {
for scope in self.scopes.iter().rev() {
for (p, uri) in scope.bindings.iter().rev() {
if p.is_empty() {
if uri.is_empty() {
return None; }
return Some(uri);
}
}
}
None
}
pub fn in_scope_namespaces(&self) -> Vec<(&str, &str)> {
let mut result: Vec<(&str, &str)> = Vec::new();
let mut seen_prefixes = std::collections::HashSet::new();
for scope in self.scopes.iter().rev() {
for (p, uri) in scope.bindings.iter().rev() {
if seen_prefixes.insert(&**p) {
result.push((&**p, &**uri));
}
}
}
result
}
pub fn depth(&self) -> usize {
self.scopes.len()
}
}
impl<'a> Default for NamespaceResolver<'a> {
fn default() -> Self {
Self::new()
}
}
pub fn build_resolver_for_node<'a>(
doc: &'a Document<'a>,
node_id: NodeId,
) -> NamespaceResolver<'a> {
let mut resolver = NamespaceResolver::new();
let mut chain = Vec::new();
let mut current = Some(node_id);
while let Some(id) = current {
chain.push(id);
current = doc.parent(id);
}
chain.reverse();
for &id in &chain {
if let Some(NodeKind::Element(elem)) = doc.node_kind(id) {
resolver.push_scope();
for (prefix, uri) in &elem.namespace_declarations {
resolver.declare(prefix.clone(), uri.clone());
}
}
}
resolver
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolve_prefix() {
let mut resolver = NamespaceResolver::new();
resolver.push_scope();
resolver.declare(
Cow::Owned("soap".to_string()),
Cow::Owned("http://www.w3.org/2003/05/soap-envelope".to_string()),
);
assert_eq!(
resolver.resolve("soap").map(|c| &**c),
Some("http://www.w3.org/2003/05/soap-envelope")
);
}
#[test]
fn test_resolve_default_namespace() {
let mut resolver = NamespaceResolver::new();
resolver.push_scope();
resolver.declare(
Cow::Owned(String::new()),
Cow::Owned("http://example.com/default".to_string()),
);
assert_eq!(
resolver.resolve_default().map(|c| &**c),
Some("http://example.com/default")
);
}
#[test]
fn test_scope_shadowing() {
let mut resolver = NamespaceResolver::new();
resolver.push_scope();
resolver.declare(
Cow::Owned("ns".to_string()),
Cow::Owned("http://first.com".to_string()),
);
assert_eq!(
resolver.resolve("ns").map(|c| &**c),
Some("http://first.com")
);
resolver.push_scope();
resolver.declare(
Cow::Owned("ns".to_string()),
Cow::Owned("http://second.com".to_string()),
);
assert_eq!(
resolver.resolve("ns").map(|c| &**c),
Some("http://second.com")
);
resolver.pop_scope();
assert_eq!(
resolver.resolve("ns").map(|c| &**c),
Some("http://first.com")
);
}
#[test]
fn test_undeclare_default_namespace() {
let mut resolver = NamespaceResolver::new();
resolver.push_scope();
resolver.declare(
Cow::Owned(String::new()),
Cow::Owned("http://example.com".to_string()),
);
assert_eq!(
resolver.resolve_default().map(|c| &**c),
Some("http://example.com")
);
resolver.push_scope();
resolver.declare(Cow::Owned(String::new()), Cow::Owned(String::new()));
assert_eq!(resolver.resolve_default(), None);
}
#[test]
fn test_builtin_xml_prefix() {
let resolver = NamespaceResolver::new();
assert_eq!(
resolver.resolve("xml").map(|c| &**c),
Some("http://www.w3.org/XML/1998/namespace")
);
}
}