use std::collections::BTreeMap;
use super::{validate_namespace_binding, NamespacePrefix, NamespaceUri, XmlResult};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NamespaceDeclaration {
prefix: Option<NamespacePrefix>,
uri: NamespaceUri,
}
impl NamespaceDeclaration {
pub fn default(uri: impl Into<String>) -> XmlResult<Self> {
let uri = uri.into();
validate_namespace_binding(None, &uri)?;
Ok(Self {
prefix: None,
uri: NamespaceUri::new(uri)?,
})
}
pub fn prefixed(prefix: impl Into<String>, uri: impl Into<String>) -> XmlResult<Self> {
let prefix = prefix.into();
let uri = uri.into();
validate_namespace_binding(Some(&prefix), &uri)?;
Ok(Self {
prefix: Some(NamespacePrefix::new(prefix)?),
uri: NamespaceUri::new(uri)?,
})
}
pub fn prefix(&self) -> Option<&NamespacePrefix> {
self.prefix.as_ref()
}
pub fn uri(&self) -> &NamespaceUri {
&self.uri
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct NamespaceTable {
default_namespace: Option<NamespaceUri>,
prefixed: BTreeMap<NamespacePrefix, NamespaceUri>,
}
impl NamespaceTable {
pub fn new() -> Self {
Self::default()
}
pub fn declare(&mut self, declaration: NamespaceDeclaration) {
match declaration.prefix {
Some(prefix) => {
self.prefixed.insert(prefix, declaration.uri);
}
None => {
self.default_namespace = Some(declaration.uri);
}
}
}
pub fn declare_default(&mut self, uri: impl Into<String>) -> XmlResult<()> {
self.declare(NamespaceDeclaration::default(uri)?);
Ok(())
}
pub fn declare_prefix(
&mut self,
prefix: impl Into<String>,
uri: impl Into<String>,
) -> XmlResult<()> {
self.declare(NamespaceDeclaration::prefixed(prefix, uri)?);
Ok(())
}
pub fn default_namespace(&self) -> Option<&NamespaceUri> {
self.default_namespace.as_ref()
}
pub fn resolve_prefix(&self, prefix: &NamespacePrefix) -> Option<&NamespaceUri> {
self.prefixed.get(prefix)
}
pub fn iter_prefixed(&self) -> impl Iterator<Item = (&NamespacePrefix, &NamespaceUri)> {
self.prefixed.iter()
}
pub fn len(&self) -> usize {
self.prefixed.len() + usize::from(self.default_namespace.is_some())
}
pub fn is_empty(&self) -> bool {
self.default_namespace.is_none() && self.prefixed.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn namespace_table_registers_and_resolves_prefixes() {
let mut table = NamespaceTable::new();
table
.declare_prefix("cbc", "urn:example:cbc")
.expect("valid namespace");
let prefix = NamespacePrefix::new("cbc").expect("valid prefix");
assert_eq!(
table.resolve_prefix(&prefix).map(NamespaceUri::as_str),
Some("urn:example:cbc")
);
}
#[test]
fn namespace_table_resolves_default_namespace() {
let mut table = NamespaceTable::new();
table
.declare_default("urn:example:document")
.expect("valid namespace");
assert_eq!(
table.default_namespace().map(NamespaceUri::as_str),
Some("urn:example:document")
);
}
#[test]
fn namespace_declaration_rejects_invalid_prefix() {
let error = NamespaceDeclaration::prefixed("", "urn:example")
.expect_err("empty namespace prefix must fail");
assert_eq!(error.kind(), &super::super::ErrorKind::InvalidName);
}
#[test]
fn namespace_declaration_respects_reserved_xml_bindings() {
NamespaceDeclaration::prefixed("xml", super::super::XML_NAMESPACE_URI)
.expect("xml prefix binding");
assert_eq!(
NamespaceDeclaration::prefixed("xml", "urn:wrong")
.expect_err("xml prefix must use XML namespace URI")
.kind(),
&super::super::ErrorKind::InvalidNamespace
);
assert_eq!(
NamespaceDeclaration::prefixed("doc", super::super::XML_NAMESPACE_URI)
.expect_err("XML namespace URI is reserved for xml prefix")
.kind(),
&super::super::ErrorKind::InvalidNamespace
);
assert_eq!(
NamespaceDeclaration::prefixed("xmlns", "urn:any")
.expect_err("xmlns prefix is reserved")
.kind(),
&super::super::ErrorKind::InvalidNamespace
);
assert_eq!(
NamespaceDeclaration::default(super::super::XMLNS_NAMESPACE_URI)
.expect_err("XMLNS namespace URI cannot be declared")
.kind(),
&super::super::ErrorKind::InvalidNamespace
);
}
#[test]
fn namespace_table_keeps_prefixed_bindings_deterministic() {
let mut table = NamespaceTable::new();
table.declare_prefix("z", "urn:z").expect("valid namespace");
table.declare_prefix("a", "urn:a").expect("valid namespace");
let prefixes = table
.iter_prefixed()
.map(|(prefix, _)| prefix.as_str())
.collect::<Vec<_>>();
assert_eq!(prefixes, vec!["a", "z"]);
}
}