Merge branch 'namespaceset' into 'master'

lumi created

add NamespaceSet implementation

See merge request !14

Change summary

Cargo.toml           |   2 
src/element.rs       | 173 ++++++++++++++++++++++++++-------------------
src/error.rs         |   5 +
src/lib.rs           |   1 
src/namespace_set.rs | 150 +++++++++++++++++++++++++++++++++++++++
src/tests.rs         |  50 +++++++++++++
6 files changed, 304 insertions(+), 77 deletions(-)

Detailed changes

Cargo.toml 🔗

@@ -1,7 +1,7 @@
 [package]
 name = "minidom"
 version = "0.5.0"
-authors = ["lumi <lumi@pew.im>", "Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>", "Bastien Orivel <eijebong+minidom@bananium.fr>"]
+authors = ["lumi <lumi@pew.im>", "Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>", "Bastien Orivel <eijebong+minidom@bananium.fr>", "Astro <astro@spaceboyz.net>"]
 description = "A small, simple DOM implementation on top of quick-xml"
 homepage = "https://gitlab.com/lumi/minidom-rs"
 repository = "https://gitlab.com/lumi/minidom-rs"

src/element.rs 🔗

@@ -4,6 +4,8 @@ use std::io:: Write;
 use std::collections::{btree_map, BTreeMap};
 
 use std::str;
+use std::rc::Rc;
+use std::borrow::Cow;
 
 use error::{Error, ErrorKind, Result};
 
@@ -17,6 +19,7 @@ use std::str::FromStr;
 use std::slice;
 
 use convert::{IntoElements, IntoAttributeValue, ElementEmitter};
+use namespace_set::NamespaceSet;
 
 /// Escape XML text
 pub fn write_escaped<W: Write>(writer: &mut W, input: &str) -> Result<()> {
@@ -84,9 +87,9 @@ impl Node {
         }
     }
 
-    fn write_to_inner<W: Write>(&self, writer: &mut W, last_namespace: &mut Option<String>) -> Result<()>{
+    fn write_to_inner<W: Write>(&self, writer: &mut W) -> Result<()>{
         match *self {
-            Node::Element(ref elmt) => elmt.write_to_inner(writer, last_namespace)?,
+            Node::Element(ref elmt) => elmt.write_to_inner(writer)?,
             Node::Text(ref s) => write_escaped(writer, s)?,
         }
 
@@ -97,8 +100,9 @@ impl Node {
 #[derive(Clone, PartialEq, Eq, Debug)]
 /// A struct representing a DOM Element.
 pub struct Element {
+    prefix: Option<String>,
     name: String,
-    namespace: Option<String>,
+    namespaces: Rc<NamespaceSet>,
     attributes: BTreeMap<String, String>,
     children: Vec<Node>,
 }
@@ -122,10 +126,10 @@ impl FromStr for Element {
 }
 
 impl Element {
-    fn new(name: String, namespace: Option<String>, attributes: BTreeMap<String, String>, children: Vec<Node>) -> Element {
+    fn new<NS: Into<NamespaceSet>>(name: String, prefix: Option<String>, namespaces: NS, attributes: BTreeMap<String, String>, children: Vec<Node>) -> Element {
         Element {
-            name: name,
-            namespace: namespace,
+            prefix, name,
+            namespaces: Rc::new(namespaces.into()),
             attributes: attributes,
             children: children,
         }
@@ -145,14 +149,16 @@ impl Element {
     ///                    .build();
     ///
     /// assert_eq!(elem.name(), "name");
-    /// assert_eq!(elem.ns(), Some("namespace"));
+    /// assert_eq!(elem.ns(), Some("namespace".to_owned()));
     /// assert_eq!(elem.attr("name"), Some("value"));
     /// assert_eq!(elem.attr("inexistent"), None);
     /// assert_eq!(elem.text(), "inner");
     /// ```
-    pub fn builder<S: Into<String>>(name: S) -> ElementBuilder {
+    pub fn builder<S: AsRef<str>>(name: S) -> ElementBuilder {
+        let (prefix, name) = split_element_name(name).unwrap();
         ElementBuilder {
-            root: Element::new(name.into(), None, BTreeMap::new(), Vec::new()),
+            root: Element::new(name, prefix, None, BTreeMap::new(), Vec::new()),
+            namespaces: Default::default(),
         }
     }
 
@@ -172,8 +178,9 @@ impl Element {
     /// ```
     pub fn bare<S: Into<String>>(name: S) -> Element {
         Element {
+            prefix: None,
             name: name.into(),
-            namespace: None,
+            namespaces: Rc::new(NamespaceSet::default()),
             attributes: BTreeMap::new(),
             children: Vec::new(),
         }
@@ -185,9 +192,8 @@ impl Element {
     }
 
     /// Returns a reference to the namespace of this element, if it has one, else `None`.
-    pub fn ns(&self) -> Option<&str> {
-        self.namespace.as_ref()
-                      .map(String::as_ref)
+    pub fn ns(&self) -> Option<String> {
+        self.namespaces.get(&self.prefix)
     }
 
     /// Returns a reference to the value of the given attribute, if it exists, else `None`.
@@ -256,8 +262,8 @@ impl Element {
     /// assert_eq!(elem.is("wrong", "wrong"), false);
     /// ```
     pub fn is<N: AsRef<str>, NS: AsRef<str>>(&self, name: N, namespace: NS) -> bool {
-        let ns = self.namespace.as_ref().map(String::as_ref);
-        self.name == name.as_ref() && ns == Some(namespace.as_ref())
+        self.name == name.as_ref() &&
+            self.namespaces.has(&self.prefix, namespace)
     }
 
     /// Parse a document from an `EventReader`.
@@ -322,27 +328,30 @@ impl Element {
 
     /// Output a document to a `Writer`.
     pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
-        let mut last_namespace = None;
         write!(writer, "<?xml version=\"1.0\" encoding=\"utf-8\"?>")?;
-        self.write_to_inner(writer, &mut last_namespace)
+        self.write_to_inner(writer)
     }
 
-    /// Output a document to a `Writer` assuming you're already in the provided namespace
-    pub fn write_to_in_namespace<W: Write>(&self, writer: &mut W, namespace: &str) -> Result<()> {
-        write!(writer, "<?xml version=\"1.0\" encoding=\"utf-8\"?>")?;
-        self.write_to_inner(writer, &mut Some(namespace.to_owned()))
-    }
-
-    fn write_to_inner<W: Write>(&self, writer: &mut W, last_namespace: &mut Option<String>) -> Result<()> {
-        write!(writer, "<")?;
-        write!(writer, "{}", self.name)?;
-
-        if let Some(ref ns) = self.namespace {
-            if *last_namespace != self.namespace {
-                write!(writer, " xmlns=\"")?;
-                write_escaped(writer, ns)?;
-                write!(writer, "\"")?;
-                *last_namespace = Some(ns.clone());
+    /// Like `write_to()` but without the `<?xml?>` prelude
+    pub fn write_to_inner<W: Write>(&self, writer: &mut W) -> Result<()> {
+        let name = match &self.prefix {
+            &None => Cow::Borrowed(&self.name),
+            &Some(ref prefix) => Cow::Owned(format!("{}:{}", prefix, self.name)),
+        };
+        write!(writer, "<{}", name)?;
+
+        for (prefix, ns) in self.namespaces.declared_ns() {
+            match prefix {
+                &None => {
+                    write!(writer, " xmlns=\"")?;
+                    write_escaped(writer, ns)?;
+                    write!(writer, "\"")?;
+                },
+                &Some(ref prefix) => {
+                    write!(writer, " xmlns:{}=\"", prefix)?;
+                    write_escaped(writer, ns)?;
+                    write!(writer, "\"")?;
+                },
             }
         }
 
@@ -360,10 +369,10 @@ impl Element {
         write!(writer, ">")?;
 
         for child in &self.children {
-            child.write_to_inner(writer, last_namespace)?;
+            child.write_to_inner(writer)?;
         }
 
-        write!(writer, "</{}>", self.name)?;
+        write!(writer, "</{}>", name)?;
         Ok(())
     }
 
@@ -472,30 +481,17 @@ impl Element {
     ///
     /// assert_eq!(child.name(), "new");
     /// ```
-    pub fn append_child(&mut self, mut child: Element) -> &mut Element {
-        if child.namespace.is_none() && self.namespace.is_some() {
-            child.namespace = self.namespace.clone();
-            child.propagate_namespaces();
-        }
+    pub fn append_child(&mut self, child: Element) -> &mut Element {
+        child.namespaces.set_parent(self.namespaces.clone());
+
         self.children.push(Node::Element(child));
         if let Node::Element(ref mut cld) = *self.children.last_mut().unwrap() {
             cld
-        }
-        else {
+        } else {
             unreachable!()
         }
     }
 
-    fn propagate_namespaces(&mut self) {
-        let ns = self.namespace.clone();
-        for child in self.children_mut() {
-            if child.namespace.is_none() {
-                child.namespace = ns.clone();
-                child.propagate_namespaces();
-            }
-        }
-    }
-
     /// Appends a text node to an `Element`.
     ///
     /// # Examples
@@ -610,29 +606,42 @@ impl Element {
     }
 }
 
+fn split_element_name<S: AsRef<str>>(s: S) -> Result<(Option<String>, String)> {
+    let name_parts = s.as_ref().split(':').collect::<Vec<&str>>();
+    match name_parts.len() {
+        2 => Ok((Some(name_parts[0].to_owned()), name_parts[1].to_owned())),
+        1 => Ok((None, name_parts[0].to_owned())),
+        _ => bail!(ErrorKind::InvalidElement),
+    }
+}
+
 fn build_element(event: &BytesStart) -> Result<Element> {
-    let mut attributes = event.attributes()
-                               .map(|o| {
-                                    let o = o?;
-                                    let key = str::from_utf8(o.key)?.to_owned();
-                                    let value = str::from_utf8(o.value)?.to_owned();
-                                    Ok((key, value))
-                                   }
-                                )
-                               .collect::<Result<BTreeMap<String, String>>>()?;
-        let mut ns_key = None;
-        for (key, _) in &attributes {
-            if key == "xmlns" || key.starts_with("xmlns:") {
-                ns_key = Some(key.clone());
+    let mut namespaces = BTreeMap::new();
+    let attributes = event.attributes()
+        .map(|o| {
+            let o = o?;
+            let key = str::from_utf8(o.key)?.to_owned();
+            let value = str::from_utf8(o.value)?.to_owned();
+            Ok((key, value))
+        })
+        .filter(|o| {
+            match o {
+                &Ok((ref key, ref value)) if key == "xmlns" => {
+                    namespaces.insert(None, value.to_owned());
+                    false
+                },
+                &Ok((ref key, ref value)) if key.starts_with("xmlns:") => {
+                    namespaces.insert(Some(key[6..].to_owned()), value.to_owned());
+                    false
+                },
+                _ => true,
             }
-        }
+        })
+        .collect::<Result<BTreeMap<String, String>>>()?;
 
-        let ns = match ns_key {
-            None => None,
-            Some(key) => attributes.remove(&key),
-        };
-        let name = str::from_utf8(event.name())?.to_owned();
-        Ok(Element::new(name, ns, attributes, Vec::new()))
+    let (prefix, name) = split_element_name(str::from_utf8(event.name())?)?;
+    let element = Element::new(name, prefix, namespaces, attributes, Vec::new());
+    Ok(element)
 }
 
 /// An iterator over references to child elements of an `Element`.
@@ -742,12 +751,14 @@ impl<'a> Iterator for AttrsMut<'a> {
 /// A builder for `Element`s.
 pub struct ElementBuilder {
     root: Element,
+    namespaces: BTreeMap<Option<String>, String>,
 }
 
 impl ElementBuilder {
     /// Sets the namespace.
     pub fn ns<S: Into<String>>(mut self, namespace: S) -> ElementBuilder {
-        self.root.namespace = Some(namespace.into());
+        self.namespaces
+            .insert(self.root.prefix.clone(), namespace.into());
         self
     }
 
@@ -768,21 +779,33 @@ impl ElementBuilder {
 
     /// Builds the `Element`.
     pub fn build(self) -> Element {
-        self.root
+        let mut element = self.root;
+        // Set namespaces
+        element.namespaces = Rc::new(NamespaceSet::from(self.namespaces));
+        // Propagate namespaces
+        for node in &element.children {
+            if let Node::Element(ref e) = *node {
+                e.namespaces.set_parent(element.namespaces.clone());
+            }
+        }
+
+        element
     }
 }
 
+#[cfg(test)]
 #[test]
 fn test_element_new() {
     use std::iter::FromIterator;
 
     let elem = Element::new( "name".to_owned()
+                           , None
                            , Some("namespace".to_owned())
                            , BTreeMap::from_iter(vec![ ("name".to_string(), "value".to_string()) ].into_iter() )
                            , Vec::new() );
 
     assert_eq!(elem.name(), "name");
-    assert_eq!(elem.ns(), Some("namespace"));
+    assert_eq!(elem.ns(), Some("namespace".to_owned()));
     assert_eq!(elem.attr("name"), Some("value"));
     assert_eq!(elem.attr("inexistent"), None);
 }

src/error.rs 🔗

@@ -26,5 +26,10 @@ error_chain! {
             description("The XML is invalid, an element was wrongly closed")
             display("the XML is invalid, an element was wrongly closed")
         }
+        /// An error which is returned when an elemet's name contains more than one colon
+        InvalidElement {
+            description("The XML element is invalid")
+            display("the XML element is invalid")
+        }
     }
 }

src/lib.rs 🔗

@@ -70,6 +70,7 @@ extern crate quick_xml;
 pub mod error;
 pub mod element;
 pub mod convert;
+mod namespace_set;
 
 #[cfg(test)] mod tests;
 

src/namespace_set.rs 🔗

@@ -0,0 +1,150 @@
+use std::collections::BTreeMap;
+use std::cell::RefCell;
+use std::rc::Rc;
+
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct NamespaceSet {
+    parent: RefCell<Option<Rc<NamespaceSet>>>,
+    namespaces: BTreeMap<Option<String>, String>,
+}
+
+impl Default for NamespaceSet {
+    fn default() -> Self {
+        NamespaceSet {
+            parent: RefCell::new(None),
+            namespaces: BTreeMap::new(),
+        }
+    }
+}
+
+impl NamespaceSet {
+    pub fn declared_ns(&self) -> &BTreeMap<Option<String>, String> {
+        &self.namespaces
+    }
+    
+    pub fn get(&self, prefix: &Option<String>) -> Option<String> {
+        match self.namespaces.get(prefix) {
+            Some(ns) => Some(ns.clone()),
+            None => match *self.parent.borrow() {
+                None => None,
+                Some(ref parent) => parent.get(prefix)
+            },
+        }
+    }
+
+    pub fn has<NS: AsRef<str>>(&self, prefix: &Option<String>, wanted_ns: NS) -> bool {
+        match self.namespaces.get(prefix) {
+            Some(ns) =>
+                ns == wanted_ns.as_ref(),
+            None => match *self.parent.borrow() {
+                None =>
+                    false,
+                Some(ref parent) =>
+                    parent.has(prefix, wanted_ns),
+            },
+        }
+    }
+
+    pub fn set_parent(&self, parent: Rc<NamespaceSet>) {
+        let mut parent_ns = self.parent.borrow_mut();
+        let new_set = parent;
+        *parent_ns = Some(new_set);
+    }
+
+}
+
+impl From<BTreeMap<Option<String>, String>> for NamespaceSet {
+    fn from(namespaces: BTreeMap<Option<String>, String>) -> Self {
+        NamespaceSet {
+            parent: RefCell::new(None),
+            namespaces: namespaces,
+        }
+    }
+}
+
+impl From<Option<String>> for NamespaceSet {
+    fn from(namespace: Option<String>) -> Self {
+        match namespace {
+            None => Self::default(),
+            Some(namespace) => Self::from(namespace),
+        }
+    }
+}
+
+impl From<String> for NamespaceSet {
+    fn from(namespace: String) -> Self {
+        let mut namespaces = BTreeMap::new();
+        namespaces.insert(None, namespace);
+
+        NamespaceSet {
+            parent: RefCell::new(None),
+            namespaces: namespaces,
+        }
+    }
+}
+
+impl From<(Option<String>, String)> for NamespaceSet {
+    fn from(prefix_namespace: (Option<String>, String)) -> Self {
+        let (prefix, namespace) = prefix_namespace;
+        let mut namespaces = BTreeMap::new();
+        namespaces.insert(prefix, namespace);
+
+        NamespaceSet {
+            parent: RefCell::new(None),
+            namespaces: namespaces,
+        }
+    }
+}
+
+impl From<(String, String)> for NamespaceSet {
+    fn from(prefix_namespace: (String, String)) -> Self {
+        let (prefix, namespace) = prefix_namespace;
+        Self::from((Some(prefix), namespace))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::rc::Rc;
+
+    #[test]
+    fn get_has() {
+        let namespaces = NamespaceSet::from("foo".to_owned());
+        assert_eq!(namespaces.get(&None), Some("foo".to_owned()));
+        assert!(namespaces.has(&None, "foo"));
+    }
+
+    #[test]
+    fn get_has_prefixed() {
+        let namespaces = NamespaceSet::from(("x".to_owned(), "bar".to_owned()));
+        assert_eq!(namespaces.get(&Some("x".to_owned())), Some("bar".to_owned()));
+        assert!(namespaces.has(&Some("x".to_owned()), "bar"));
+    }
+
+    #[test]
+    fn get_has_recursive() {
+        let mut parent = NamespaceSet::from("foo".to_owned());
+        for _ in 0..1000 {
+            let namespaces = NamespaceSet::default();
+            namespaces.set_parent(Rc::new(parent));
+            assert_eq!(namespaces.get(&None), Some("foo".to_owned()));
+            assert!(namespaces.has(&None, "foo"));
+            parent = namespaces;
+        }
+    }
+
+    #[test]
+    fn get_has_prefixed_recursive() {
+        let mut parent = NamespaceSet::from(("x".to_owned(), "bar".to_owned()));
+        for _ in 0..1000 {
+            let namespaces = NamespaceSet::default();
+            namespaces.set_parent(Rc::new(parent));
+            assert_eq!(namespaces.get(&Some("x".to_owned())), Some("bar".to_owned()));
+            assert!(namespaces.has(&Some("x".to_owned()), "bar"));
+            parent = namespaces;
+        }
+    }
+
+}

src/tests.rs 🔗

@@ -80,7 +80,7 @@ fn builder_works() {
                        .append("e")
                        .build();
     assert_eq!(elem.name(), "a");
-    assert_eq!(elem.ns(), Some("b"));
+    assert_eq!(elem.ns(), Some("b".to_owned()));
     assert_eq!(elem.attr("c"), Some("d"));
     assert_eq!(elem.attr("x"), None);
     assert_eq!(elem.text(), "e");
@@ -115,6 +115,7 @@ fn namespace_propagation_works() {
     let grandchild = Element::bare("grandchild");
     child.append_child(grandchild);
     root.append_child(child);
+
     assert_eq!(root.get_child("child", "root_ns").unwrap().ns(), root.ns());
     assert_eq!(root.get_child("child", "root_ns").unwrap()
                    .get_child("grandchild", "root_ns").unwrap()
@@ -149,3 +150,50 @@ fn wrongly_closed_elements_error() {
     let elem1 = "<a><c><d/></c></a>".parse::<Element>();
     assert!(elem1.is_ok());
 }
+
+#[test]
+fn namespace_simple() {
+    let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
+    assert_eq!(elem.name(), "message");
+    assert_eq!(elem.ns(), Some("jabber:client".to_owned()));
+}
+
+#[test]
+fn namespace_prefixed() {
+    let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"
+        .parse().unwrap();
+    assert_eq!(elem.name(), "features");
+    assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams".to_owned()));
+}
+
+#[test]
+fn namespace_inherited_simple() {
+    let elem: Element = "<stream xmlns='jabber:client'><message/></stream>".parse().unwrap();
+    assert_eq!(elem.name(), "stream");
+    assert_eq!(elem.ns(), Some("jabber:client".to_owned()));
+    let child = elem.children().next().unwrap();
+    assert_eq!(child.name(), "message");
+    assert_eq!(child.ns(), Some("jabber:client".to_owned()));
+}
+
+#[test]
+fn namespace_inherited_prefixed1() {
+    let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'><message/></stream:features>"
+        .parse().unwrap();
+    assert_eq!(elem.name(), "features");
+    assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams".to_owned()));
+    let child = elem.children().next().unwrap();
+    assert_eq!(child.name(), "message");
+    assert_eq!(child.ns(), Some("jabber:client".to_owned()));
+}
+
+#[test]
+fn namespace_inherited_prefixed2() {
+    let elem: Element = "<stream xmlns='http://etherx.jabber.org/streams' xmlns:jabber='jabber:client'><jabber:message/></stream>"
+        .parse().unwrap();
+    assert_eq!(elem.name(), "stream");
+    assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams".to_owned()));
+    let child = elem.children().next().unwrap();
+    assert_eq!(child.name(), "message");
+    assert_eq!(child.ns(), Some("jabber:client".to_owned()));
+}