xdoc-rs 0.1.1

Declarative XML engine for Rust
Documentation
use std::collections::BTreeMap;

use super::{validate_namespace_binding, NamespacePrefix, NamespaceUri, XmlResult};

/// Namespace declaration attached to an XML element or namespace table.
#[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
    }
}

/// Namespace bindings available while constructing, parsing, or writing XML.
#[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"]);
    }
}