minidom: forcing a namespace on Element. Stop requiring prefixes.

Maxime “pep” Buquet created

Below is what I think I did.

A few changes:
- Change prefixes to be something less important in the API.
- Rework the Element struct to force a namespace. In XMPP everything is
  namespaced.
- Remove parent ref on what was previously NamespaceSet and is now
  Prefixes.

More specifically this means `Element::new` has changed to require
`Element`'s new new properties as parameters. `Element::builder` and
`Element::bare` now require a namespace unconditionally.
`Element::prefix` has been removed.

This new API is based on the fact that prefixes are non-essential
(really just an implementation detail) and shouldn't be visible to the
user. It is possible nonetheless to set custom prefixes for
compatibility reasons with `ElementBuilder::prefix`. **A prefix is
firstly mapped to a namespace, and then attached to an element**, there
cannot be a prefix without a namespace.

Prefix inheritance is used if possible but for the case with no
prefix ("xmlns") to be reused, we only check custom prefixes declared on
the tag itself and not ascendants. If it's already used then we generate
prefixes (ns0, ns1, ..) checking on what has been declared on all
ascendants (plus of course those already set on the current tag).

Example API:

```rust
let mut elem = ElementBuilder("stream", "http://etherx.jabber.org/streams")
  .prefix(Some(String::from("stream")), "http://etherx.jabber.org/streams)
  .prefix(None, "jabber:client")
  .attr(..)
  .build();

assert_eq!(elem.ns(), String::from("http://etherx.jabber.org/streams"));
```

See also the few tests added in src/tests.

TODO: Fix inconsistencies wrt. "prefix:name" format provided as a name
when creating an Element with `Element::new` or `Element::bare`.
`Element::builder` already handles this as it should, splitting name and
prefix.
TODO: Change `Element::name` method to `Element::local_name` to make it
more explicit.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>

Change summary

minidom-rs/src/element.rs       | 293 +++++++++++++++++++++-------------
minidom-rs/src/error.rs         |   8 
minidom-rs/src/lib.rs           |   5 
minidom-rs/src/namespace_set.rs | 217 -------------------------
minidom-rs/src/namespaces.rs    |  38 ++++
minidom-rs/src/node.rs          |  17 +
minidom-rs/src/parser.rs        | 184 +++++++++++++++++++++
minidom-rs/src/prefixes.rs      |  96 +++++++++++
minidom-rs/src/tests.rs         | 230 +++++++++++++++++++++++----
9 files changed, 709 insertions(+), 379 deletions(-)

Detailed changes

minidom-rs/src/element.rs 🔗

@@ -14,7 +14,8 @@
 
 use crate::convert::IntoAttributeValue;
 use crate::error::{Error, Result};
-use crate::namespace_set::{NSChoice, NamespaceSet};
+use crate::namespaces::NSChoice;
+use crate::prefixes::Prefixes;
 use crate::node::Node;
 
 use std::collections::{btree_map, BTreeMap};
@@ -83,9 +84,9 @@ pub fn escape(raw: &[u8]) -> Cow<[u8]> {
 #[derive(Clone, Eq, Debug)]
 /// A struct representing a DOM Element.
 pub struct Element {
-    prefix: Option<String>,
     name: String,
-    namespaces: Rc<NamespaceSet>,
+    namespace: String,
+    prefixes: Rc<Prefixes>,
     attributes: BTreeMap<String, String>,
     children: Vec<Node>,
 }
@@ -121,17 +122,18 @@ impl PartialEq for Element {
 }
 
 impl Element {
-    fn new<NS: Into<NamespaceSet>>(
+    fn new<P: Into<Prefixes>>(
         name: String,
-        prefix: Option<String>,
-        namespaces: NS,
+        namespace: String,
+        prefixes: P,
         attributes: BTreeMap<String, String>,
         children: Vec<Node>,
     ) -> Element {
+        // TODO split name and possible prefix.
         Element {
-            prefix,
             name,
-            namespaces: Rc::new(namespaces.into()),
+            namespace,
+            prefixes: Rc::new(prefixes.into()),
             attributes,
             children,
         }
@@ -144,23 +146,31 @@ impl Element {
     /// ```rust
     /// use minidom::Element;
     ///
-    /// let elem = Element::builder("name")
-    ///                    .ns("namespace")
+    /// let elem = Element::builder("name", "namespace")
     ///                    .attr("name", "value")
     ///                    .append("inner")
     ///                    .build();
     ///
     /// assert_eq!(elem.name(), "name");
-    /// assert_eq!(elem.ns(), Some("namespace".to_owned()));
+    /// assert_eq!(elem.ns(), "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: AsRef<str>>(name: S) -> ElementBuilder {
+    pub fn builder<S: AsRef<str>, NS: Into<String>>(name: S, namespace: NS) -> ElementBuilder {
         let (prefix, name) = split_element_name(name).unwrap();
+        let namespace: String = namespace.into();
+        let prefixes: BTreeMap<String, Option<String>> = match prefix {
+            None => Default::default(),
+            Some(_) => {
+                let mut prefixes: BTreeMap<String, Option<String>> = BTreeMap::new();
+                prefixes.insert(namespace.clone(), prefix);
+                prefixes
+            },
+        };
         ElementBuilder {
-            root: Element::new(name, prefix, None, BTreeMap::new(), Vec::new()),
-            namespaces: Default::default(),
+            root: Element::new(name, namespace, None, BTreeMap::new(), Vec::new()),
+            prefixes,
         }
     }
 
@@ -171,47 +181,33 @@ impl Element {
     /// ```rust
     /// use minidom::Element;
     ///
-    /// let bare = Element::bare("name");
+    /// let bare = Element::bare("name", "namespace");
     ///
     /// assert_eq!(bare.name(), "name");
-    /// assert_eq!(bare.ns(), None);
+    /// assert_eq!(bare.ns(), "namespace");
     /// assert_eq!(bare.attr("name"), None);
     /// assert_eq!(bare.text(), "");
     /// ```
-    pub fn bare<S: Into<String>>(name: S) -> Element {
+    pub fn bare<S: Into<String>, NS: Into<String>>(name: S, namespace: NS) -> Element {
+        // TODO split name and possible prefix.
         Element {
-            prefix: None,
             name: name.into(),
-            namespaces: Rc::new(NamespaceSet::default()),
+            namespace: namespace.into(),
+            prefixes: Rc::new(Prefixes::default()),
             attributes: BTreeMap::new(),
             children: Vec::new(),
         }
     }
 
     /// Returns a reference to the name of this element.
+    // TODO: rename local_name
     pub fn name(&self) -> &str {
         &self.name
     }
 
-    /// Returns a reference to the prefix of this element.
-    ///
-    /// # Examples
-    /// ```rust
-    /// use minidom::Element;
-    ///
-    /// let elem = Element::builder("prefix:name")
-    ///                    .build();
-    ///
-    /// assert_eq!(elem.name(), "name");
-    /// assert_eq!(elem.prefix(), Some("prefix"));
-    /// ```
-    pub fn prefix(&self) -> Option<&str> {
-        self.prefix.as_ref().map(String::as_ref)
-    }
-
     /// Returns a reference to the namespace of this element, if it has one, else `None`.
-    pub fn ns(&self) -> Option<String> {
-        self.namespaces.get(&self.prefix)
+    pub fn ns(&self) -> String {
+        self.namespace.clone()
     }
 
     /// Returns a reference to the value of the given attribute, if it exists, else `None`.
@@ -229,7 +225,7 @@ impl Element {
     /// ```rust
     /// use minidom::Element;
     ///
-    /// let elm: Element = "<elem a=\"b\" />".parse().unwrap();
+    /// let elm: Element = "<elem xmlns=\"ns1\" a=\"b\" />".parse().unwrap();
     ///
     /// let mut iter = elm.attrs();
     ///
@@ -273,7 +269,7 @@ impl Element {
     /// ```rust
     /// use minidom::{Element, NSChoice};
     ///
-    /// let elem = Element::builder("name").ns("namespace").build();
+    /// let elem = Element::builder("name", "namespace").build();
     ///
     /// assert_eq!(elem.is("name", "namespace"), true);
     /// assert_eq!(elem.is("name", "wrong"), false);
@@ -284,13 +280,9 @@ impl Element {
     /// assert_eq!(elem.is("name", NSChoice::OneOf("foo")), false);
     /// assert_eq!(elem.is("name", NSChoice::AnyOf(&["foo", "namespace"])), true);
     /// assert_eq!(elem.is("name", NSChoice::Any), true);
-    ///
-    /// let elem2 = Element::builder("name").build();
-    ///
-    /// assert_eq!(elem2.is("name", NSChoice::Any), true);
     /// ```
     pub fn is<'a, N: AsRef<str>, NS: Into<NSChoice<'a>>>(&self, name: N, namespace: NS) -> bool {
-        self.name == name.as_ref() && self.has_ns(namespace)
+        self.name == name.as_ref() && namespace.into().compare(self.namespace.as_ref())
     }
 
     /// Returns whether the element has the given namespace.
@@ -300,7 +292,7 @@ impl Element {
     /// ```rust
     /// use minidom::{Element, NSChoice};
     ///
-    /// let elem = Element::builder("name").ns("namespace").build();
+    /// let elem = Element::builder("name", "namespace").build();
     ///
     /// assert_eq!(elem.has_ns("namespace"), true);
     /// assert_eq!(elem.has_ns("wrong"), false);
@@ -309,24 +301,21 @@ impl Element {
     /// assert_eq!(elem.has_ns(NSChoice::OneOf("foo")), false);
     /// assert_eq!(elem.has_ns(NSChoice::AnyOf(&["foo", "namespace"])), true);
     /// assert_eq!(elem.has_ns(NSChoice::Any), true);
-    ///
-    /// let elem2 = Element::builder("name").build();
-    ///
-    /// assert_eq!(elem2.has_ns(NSChoice::Any), true);
     /// ```
     pub fn has_ns<'a, NS: Into<NSChoice<'a>>>(&self, namespace: NS) -> bool {
-        self.namespaces.has(&self.prefix, namespace)
+        namespace.into().compare(self.namespace.as_ref())
     }
 
     /// Parse a document from an `EventReader`.
     pub fn from_reader<R: BufRead>(reader: &mut EventReader<R>) -> Result<Element> {
         let mut buf = Vec::new();
 
+        let mut prefixes = BTreeMap::new();
         let root: Element = loop {
             let e = reader.read_event(&mut buf)?;
             match e {
                 Event::Empty(ref e) | Event::Start(ref e) => {
-                    break build_element(reader, e)?;
+                    break build_element(reader, e, &mut prefixes)?;
                 }
                 Event::Eof => {
                     return Err(Error::EndOfDocument);
@@ -344,22 +333,27 @@ impl Element {
         };
 
         let mut stack = vec![root];
+        let mut prefix_stack = vec![prefixes];
 
         loop {
             match reader.read_event(&mut buf)? {
                 Event::Empty(ref e) => {
-                    let elem = build_element(reader, e)?;
+                    let mut prefixes = prefix_stack.last().unwrap().clone();
+                    let elem = build_element(reader, e, &mut prefixes)?;
                     // Since there is no Event::End after, directly append it to the current node
                     stack.last_mut().unwrap().append_child(elem);
                 }
                 Event::Start(ref e) => {
-                    let elem = build_element(reader, e)?;
+                    let mut prefixes = prefix_stack.last().unwrap().clone();
+                    let elem = build_element(reader, e, &mut prefixes)?;
                     stack.push(elem);
+                    prefix_stack.push(prefixes);
                 }
                 Event::End(ref e) => {
                     if stack.len() <= 1 {
                         break;
                     }
+                    prefix_stack.pop().unwrap();
                     let elem = stack.pop().unwrap();
                     if let Some(to) = stack.last_mut() {
                         // TODO: check whether this is correct, we are comparing &[u8]s, not &strs
@@ -368,13 +362,13 @@ impl Element {
                         let possible_prefix = split_iter.next().unwrap(); // Can't be empty.
                         match split_iter.next() {
                             Some(name) => {
-                                match elem.prefix() {
-                                    Some(prefix) => {
+                                match elem.prefixes.get(&elem.namespace) {
+                                    Some(Some(prefix)) => {
                                         if possible_prefix != prefix.as_bytes() {
                                             return Err(Error::InvalidElementClosed);
                                         }
                                     }
-                                    None => {
+                                    _ => {
                                         return Err(Error::InvalidElementClosed);
                                     }
                                 }
@@ -383,8 +377,9 @@ impl Element {
                                 }
                             }
                             None => {
-                                if elem.prefix().is_some() {
-                                    return Err(Error::InvalidElementClosed);
+                                match elem.prefixes.get(&elem.namespace) {
+                                    Some(Some(_)) => return Err(Error::InvalidElementClosed),
+                                    _ => (),
                                 }
                                 if possible_prefix != elem.name().as_bytes() {
                                     return Err(Error::InvalidElementClosed);
@@ -430,32 +425,93 @@ impl Element {
 
     /// Output the document to quick-xml `Writer`
     pub fn to_writer<W: Write>(&self, writer: &mut EventWriter<W>) -> Result<()> {
-        self.write_to_inner(writer)
+        self.write_to_inner(writer, &mut BTreeMap::new())
     }
 
     /// Output the document to quick-xml `Writer`
     pub fn to_writer_decl<W: Write>(&self, writer: &mut EventWriter<W>) -> Result<()> {
         writer.write_event(Event::Decl(BytesDecl::new(b"1.0", Some(b"utf-8"), None)))?;
-        self.write_to_inner(writer)
+        self.write_to_inner(writer, &mut BTreeMap::new())
     }
 
     /// Like `write_to()` but without the `<?xml?>` prelude
-    pub fn write_to_inner<W: Write>(&self, writer: &mut EventWriter<W>) -> Result<()> {
-        let name = match self.prefix {
-            None => Cow::Borrowed(&self.name),
-            Some(ref prefix) => Cow::Owned(format!("{}:{}", prefix, self.name)),
+    pub fn write_to_inner<W: Write>(&self, writer: &mut EventWriter<W>, all_prefixes: &mut BTreeMap<Option<String>, String>) -> Result<()> {
+        let local_prefixes = self.prefixes.declared_prefixes();
+
+        // Element namespace
+        // If the element prefix hasn't been set yet via a custom prefix, add it.
+        let mut existing_self_prefix: Option<Option<String>> = None;
+        if let Some(prefix) = local_prefixes.get(&self.namespace) {
+            existing_self_prefix = Some(prefix.clone());
+        } else {
+            for (prefix, ns) in all_prefixes.iter() {
+                if ns == &self.namespace {
+                    existing_self_prefix = Some(prefix.clone());
+                }
+            }
+        }
+
+        let mut all_keys = all_prefixes.keys().cloned();
+        let mut local_keys = local_prefixes.values().cloned();
+        let self_prefix: (Option<String>, bool) = match existing_self_prefix {
+            // No prefix exists already for our namespace
+            None => {
+                if local_keys.find(|p| p == &None).is_none() {
+                    // Use the None prefix if available
+                    (None, true)
+                } else {
+                    // Otherwise generate one. Check if it isn't already used, if so increase the
+                    // number until we find a suitable one.
+                    let mut prefix_n = 0u8;
+                    while let Some(_) = all_keys.find(|p| p == &Some(format!("ns{}", prefix_n))) {
+                        prefix_n += 1;
+                    }
+                    (Some(format!("ns{}", prefix_n)), true)
+                }
+            },
+            // Some prefix has already been declared (or is going to be) for our namespace. We
+            // don't need to declare a new one. We do however need to remember which one to use in
+            // the tag name.
+            Some(prefix) => (prefix, false),
         };
 
+        let name = match self_prefix {
+            (Some(ref prefix), _) => Cow::Owned(format!("{}:{}", prefix, self.name)),
+            _ => Cow::Borrowed(&self.name),
+        };
         let mut start = BytesStart::borrowed(name.as_bytes(), name.len());
-        for (prefix, ns) in self.namespaces.declared_ns() {
-            match *prefix {
-                None => start.push_attribute(("xmlns", ns.as_ref())),
-                Some(ref prefix) => {
-                    let key = format!("xmlns:{}", prefix);
-                    start.push_attribute((key.as_bytes(), ns.as_bytes()))
-                }
+
+        // Write self prefix if necessary
+        match self_prefix {
+            (Some(ref p), true) => {
+                let key = format!("xmlns:{}", p);
+                start.push_attribute((key.as_bytes(), self.namespace.as_bytes()));
+                all_prefixes.insert(self_prefix.0, self.namespace.clone());
+            }
+            (None, true) => {
+                let key = format!("xmlns");
+                start.push_attribute((key.as_bytes(), self.namespace.as_bytes()));
+                all_prefixes.insert(self_prefix.0, self.namespace.clone());
+            },
+            _ => (),
+        };
+
+        // Custom prefixes/namespace sets
+        for (ns, prefix) in local_prefixes {
+            match all_prefixes.get(prefix) {
+                p @ Some(_) if p == prefix.as_ref() => (),
+                _ => {
+                    let key = match prefix {
+                        None => String::from("xmlns"),
+                        Some(p) => format!("xmlns:{}", p),
+                    };
+
+                    start.push_attribute((key.as_bytes(), ns.as_ref()));
+                    all_prefixes.insert(prefix.clone(), ns.clone());
+                },
             }
         }
+
         for (key, value) in &self.attributes {
             start.push_attribute((key.as_bytes(), escape(value.as_bytes()).as_ref()));
         }
@@ -468,7 +524,7 @@ impl Element {
         writer.write_event(Event::Start(start))?;
 
         for child in &self.children {
-            child.write_to_inner(writer)?;
+            child.write_to_inner(writer, all_prefixes)?;
         }
 
         writer.write_event(Event::End(BytesEnd::borrowed(name.as_bytes())))?;
@@ -482,7 +538,7 @@ impl Element {
     /// ```rust
     /// use minidom::Element;
     ///
-    /// let elem: Element = "<root>a<c1 />b<c2 />c</root>".parse().unwrap();
+    /// let elem: Element = "<root xmlns=\"ns1\">a<c1 />b<c2 />c</root>".parse().unwrap();
     ///
     /// let mut iter = elem.nodes();
     ///
@@ -511,7 +567,7 @@ impl Element {
     /// ```rust
     /// use minidom::Element;
     ///
-    /// let elem: Element = "<root>hello<child1 />this<child2 />is<child3 />ignored</root>".parse().unwrap();
+    /// let elem: Element = "<root xmlns=\"ns1\">hello<child1 xmlns=\"ns1\"/>this<child2 xmlns=\"ns1\"/>is<child3 xmlns=\"ns1\"/>ignored</root>".parse().unwrap();
     ///
     /// let mut iter = elem.children();
     /// assert_eq!(iter.next().unwrap().name(), "child1");
@@ -541,7 +597,7 @@ impl Element {
     /// ```rust
     /// use minidom::Element;
     ///
-    /// let elem: Element = "<root>hello<c /> world!</root>".parse().unwrap();
+    /// let elem: Element = "<root xmlns=\"ns1\">hello<c /> world!</root>".parse().unwrap();
     ///
     /// let mut iter = elem.texts();
     /// assert_eq!(iter.next().unwrap(), "hello");
@@ -570,11 +626,11 @@ impl Element {
     /// ```rust
     /// use minidom::Element;
     ///
-    /// let mut elem = Element::bare("root");
+    /// let mut elem = Element::bare("root", "ns1");
     ///
     /// assert_eq!(elem.children().count(), 0);
     ///
-    /// elem.append_child(Element::bare("child"));
+    /// elem.append_child(Element::bare("child", "ns1"));
     ///
     /// {
     ///     let mut iter = elem.children();
@@ -582,13 +638,11 @@ impl Element {
     ///     assert_eq!(iter.next(), None);
     /// }
     ///
-    /// let child = elem.append_child(Element::bare("new"));
+    /// let child = elem.append_child(Element::bare("new", "ns1"));
     ///
     /// assert_eq!(child.name(), "new");
     /// ```
     pub fn append_child(&mut self, child: Element) -> &mut Element {
-        child.namespaces.set_parent(Rc::clone(&self.namespaces));
-
         self.children.push(Node::Element(child));
         if let Node::Element(ref mut cld) = *self.children.last_mut().unwrap() {
             cld
@@ -604,7 +658,7 @@ impl Element {
     /// ```rust
     /// use minidom::Element;
     ///
-    /// let mut elem = Element::bare("node");
+    /// let mut elem = Element::bare("node", "ns1");
     ///
     /// assert_eq!(elem.text(), "");
     ///
@@ -623,7 +677,7 @@ impl Element {
     /// ```rust
     /// use minidom::{Element, Node};
     ///
-    /// let mut elem = Element::bare("node");
+    /// let mut elem = Element::bare("node", "ns1");
     ///
     /// elem.append_node(Node::Text("hello".to_owned()));
     ///
@@ -640,7 +694,7 @@ impl Element {
     /// ```rust
     /// use minidom::Element;
     ///
-    /// let elem: Element = "<node>hello,<split /> world!</node>".parse().unwrap();
+    /// let elem: Element = "<node xmlns=\"ns1\">hello,<split /> world!</node>".parse().unwrap();
     ///
     /// assert_eq!(elem.text(), "hello, world!");
     /// ```
@@ -656,7 +710,7 @@ impl Element {
     /// ```rust
     /// use minidom::{Element, NSChoice};
     ///
-    /// let elem: Element = r#"<node xmlns="ns"><a /><a xmlns="other_ns" /><b /></node>"#.parse().unwrap();
+    /// let elem: Element = r#"<node xmlns="ns"><a/><a xmlns="other_ns" /><b/></node>"#.parse().unwrap();
     /// assert!(elem.get_child("a", "ns").unwrap().is("a", "ns"));
     /// assert!(elem.get_child("a", "other_ns").unwrap().is("a", "other_ns"));
     /// assert!(elem.get_child("b", "ns").unwrap().is("b", "ns"));
@@ -763,8 +817,10 @@ fn split_element_name<S: AsRef<str>>(s: S) -> Result<(Option<String>, String)> {
     }
 }
 
-fn build_element<R: BufRead>(reader: &EventReader<R>, event: &BytesStart) -> Result<Element> {
-    let mut namespaces = BTreeMap::new();
+fn build_element<R: BufRead>(reader: &EventReader<R>, event: &BytesStart, prefixes: &mut BTreeMap<String, Option<String>>) -> Result<Element> {
+    let (prefix, name) = split_element_name(str::from_utf8(event.name())?)?;
+    let mut local_prefixes = BTreeMap::new();
+
     let attributes = event
         .attributes()
         .map(|o| {
@@ -775,19 +831,33 @@ fn build_element<R: BufRead>(reader: &EventReader<R>, event: &BytesStart) -> Res
         })
         .filter(|o| match *o {
             Ok((ref key, ref value)) if key == "xmlns" => {
-                namespaces.insert(None, value.to_owned());
+                local_prefixes.insert(value.clone(), None);
+                prefixes.insert(value.clone(), None);
                 false
             }
             Ok((ref key, ref value)) if key.starts_with("xmlns:") => {
-                namespaces.insert(Some(key[6..].to_owned()), value.to_owned());
+                local_prefixes.insert(value.to_owned(), Some(key[6..].to_owned()));
+                prefixes.insert(value.to_owned(), Some(key[6..].to_owned()));
                 false
             }
             _ => true,
         })
         .collect::<Result<BTreeMap<String, String>>>()?;
 
-    let (prefix, name) = split_element_name(str::from_utf8(event.name())?)?;
-    let element = Element::new(name, prefix, namespaces, attributes, Vec::new());
+    let namespace: String = {
+        let mut tmp: Option<String> = None;
+
+        for (k, v) in local_prefixes.iter().chain(prefixes.iter()) {
+            if v == &prefix {
+                tmp = Some(k.clone());
+                break;
+            }
+        }
+
+        tmp.ok_or(Error::MissingNamespace)?
+    };
+
+    let element = Element::new(name, namespace, local_prefixes, attributes, Vec::new());
     Ok(element)
 }
 
@@ -898,14 +968,13 @@ impl<'a> Iterator for AttrsMut<'a> {
 /// A builder for `Element`s.
 pub struct ElementBuilder {
     root: Element,
-    namespaces: BTreeMap<Option<String>, String>,
+    prefixes: BTreeMap<String, Option<String>>,
 }
 
 impl ElementBuilder {
-    /// Sets the namespace.
-    pub fn ns<S: Into<String>>(mut self, namespace: S) -> ElementBuilder {
-        self.namespaces
-            .insert(self.root.prefix.clone(), namespace.into());
+    /// Sets a custom prefix.
+    pub fn prefix<S: Into<String>>(mut self, prefix: Option<String>, namespace: S) -> ElementBuilder {
+        self.prefixes.insert(namespace.into(), prefix);
         self
     }
 
@@ -940,13 +1009,7 @@ impl ElementBuilder {
     pub fn build(self) -> Element {
         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(Rc::clone(&element.namespaces));
-            }
-        }
+        element.prefixes = Rc::new(Prefixes::from(self.prefixes));
         element
     }
 }
@@ -961,49 +1024,49 @@ mod tests {
 
         let elem = Element::new(
             "name".to_owned(),
-            None,
-            Some("namespace".to_owned()),
+            "namespace".to_owned(),
+            (None, "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".to_owned()));
+        assert_eq!(elem.ns(), "namespace".to_owned());
         assert_eq!(elem.attr("name"), Some("value"));
         assert_eq!(elem.attr("inexistent"), None);
     }
 
     #[test]
     fn test_from_reader_simple() {
-        let xml = "<foo></foo>";
+        let xml = "<foo xmlns='ns1'></foo>";
         let mut reader = EventReader::from_str(xml);
         let elem = Element::from_reader(&mut reader);
 
-        let elem2 = Element::builder("foo").build();
+        let elem2 = Element::builder("foo", "ns1").build();
 
         assert_eq!(elem.unwrap(), elem2);
     }
 
     #[test]
     fn test_from_reader_nested() {
-        let xml = "<foo><bar baz='qxx' /></foo>";
+        let xml = "<foo xmlns='ns1'><bar xmlns='ns1' baz='qxx' /></foo>";
         let mut reader = EventReader::from_str(xml);
         let elem = Element::from_reader(&mut reader);
 
-        let nested = Element::builder("bar").attr("baz", "qxx").build();
-        let elem2 = Element::builder("foo").append(nested).build();
+        let nested = Element::builder("bar", "ns1").attr("baz", "qxx").build();
+        let elem2 = Element::builder("foo", "ns1").append(nested).build();
 
         assert_eq!(elem.unwrap(), elem2);
     }
 
     #[test]
     fn test_from_reader_with_prefix() {
-        let xml = "<foo><prefix:bar baz='qxx' /></foo>";
+        let xml = "<foo xmlns='ns1'><prefix:bar xmlns:prefix='ns1' baz='qxx' /></foo>";
         let mut reader = EventReader::from_str(xml);
         let elem = Element::from_reader(&mut reader);
 
-        let nested = Element::builder("prefix:bar").attr("baz", "qxx").build();
-        let elem2 = Element::builder("foo").append(nested).build();
+        let nested = Element::builder("prefix:bar", "ns1").attr("baz", "qxx").build();
+        let elem2 = Element::builder("foo", "ns1").append(nested).build();
 
         assert_eq!(elem.unwrap(), elem2);
     }
@@ -1022,7 +1085,7 @@ mod tests {
 
     #[test]
     fn does_not_unescape_cdata() {
-        let xml = "<test><![CDATA[&apos;&gt;blah<blah>]]></test>";
+        let xml = "<test xmlns='test'><![CDATA[&apos;&gt;blah<blah>]]></test>";
         let mut reader = EventReader::from_str(xml);
         let elem = Element::from_reader(&mut reader).unwrap();
         assert_eq!(elem.text(), "&apos;&gt;blah<blah>");
@@ -1030,7 +1093,7 @@ mod tests {
 
     #[test]
     fn test_compare_all_ns() {
-        let xml = "<foo xmlns='foo' xmlns:bar='baz'><bar:meh/></foo>";
+        let xml = "<foo xmlns='foo' xmlns:bar='baz'><bar:meh xmlns:bar='baz' /></foo>";
         let mut reader = EventReader::from_str(xml);
         let elem = Element::from_reader(&mut reader).unwrap();
 

minidom-rs/src/error.rs 🔗

@@ -35,6 +35,9 @@ pub enum Error {
     /// An error which is returned when an elemet's name contains more than one colon
     InvalidElement,
 
+    /// An error which is returned when an element doesn't contain a namespace
+    MissingNamespace,
+
     /// An error which is returned when a comment is to be parsed by minidom
     NoComments,
 }
@@ -48,6 +51,7 @@ impl StdError for Error {
             Error::EndOfDocument => None,
             Error::InvalidElementClosed => None,
             Error::InvalidElement => None,
+            Error::MissingNamespace => None,
             Error::NoComments => None,
         }
     }
@@ -66,6 +70,10 @@ impl std::fmt::Display for Error {
                 write!(fmt, "the XML is invalid, an element was wrongly closed")
             }
             Error::InvalidElement => write!(fmt, "the XML element is invalid"),
+            Error::MissingNamespace => write!(
+                fmt,
+                "the XML element is missing a namespace",
+            ),
             Error::NoComments => write!(
                 fmt,
                 "a comment has been found even though comments are forbidden"

minidom-rs/src/lib.rs 🔗

@@ -79,7 +79,8 @@ pub use quick_xml;
 pub mod convert;
 pub mod element;
 pub mod error;
-mod namespace_set;
+mod namespaces;
+mod prefixes;
 pub mod node;
 
 #[cfg(test)]
@@ -88,5 +89,5 @@ mod tests;
 pub use convert::IntoAttributeValue;
 pub use element::{Children, ChildrenMut, Element, ElementBuilder};
 pub use error::{Error, Result};
-pub use namespace_set::NSChoice;
+pub use namespaces::NSChoice;
 pub use node::Node;

minidom-rs/src/namespace_set.rs 🔗

@@ -1,217 +0,0 @@
-// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
-// Copyright (c) 2020 Astro <astro@spaceboyz.net>
-// Copyright (c) 2020 Maxime “pep” Buquet <pep@bouah.net>
-// Copyright (c) 2020 Xidorn Quan <me@upsuper.org>
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-use std::cell::RefCell;
-use std::collections::BTreeMap;
-use std::fmt;
-use std::rc::Rc;
-
-#[derive(Clone, Copy, PartialEq, Eq, Debug)]
-/// Use to compare namespaces
-pub enum NSChoice<'a> {
-    /// The element's namespace must match the specified namespace
-    OneOf(&'a str),
-    /// The element's namespace must be in the specified vector
-    AnyOf(&'a [&'a str]),
-    /// The element can have any namespace, or no namespace
-    Any,
-}
-
-impl<'a> From<&'a str> for NSChoice<'a> {
-    fn from(ns: &'a str) -> NSChoice<'a> {
-        NSChoice::OneOf(ns)
-    }
-}
-
-impl<'a> NSChoice<'a> {
-    fn compare(&self, ns: Option<&str>) -> bool {
-        match (ns, &self) {
-            (None, NSChoice::Any) => true,
-            (None, NSChoice::OneOf(_)) | (None, NSChoice::AnyOf(_)) => false,
-            (Some(_), NSChoice::Any) => true,
-            (Some(ns), NSChoice::OneOf(wanted_ns)) => &ns == wanted_ns,
-            (Some(ns), NSChoice::AnyOf(wanted_nss)) => wanted_nss.iter().any(|w| &ns == w),
-        }
-    }
-}
-
-#[derive(Clone, 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 fmt::Debug for NamespaceSet {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "NamespaceSet(")?;
-        for (prefix, namespace) in &self.namespaces {
-            write!(
-                f,
-                "xmlns{}={:?}, ",
-                match prefix {
-                    None => String::new(),
-                    Some(prefix) => format!(":{}", prefix),
-                },
-                namespace
-            )?;
-        }
-        write!(f, "parent: {:?})", *self.parent.borrow())
-    }
-}
-
-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<'a, NS: Into<NSChoice<'a>>>(&self, prefix: &Option<String>, wanted_ns: NS) -> bool {
-        match self.namespaces.get(prefix) {
-            Some(ns) => wanted_ns.into().compare(Some(ns)),
-            None => match *self.parent.borrow() {
-                None => wanted_ns.into().compare(None),
-                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,
-        }
-    }
-}
-
-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,
-        }
-    }
-}
-
-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,
-        }
-    }
-}
-
-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::*;
-
-    #[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;
-        }
-    }
-
-    #[test]
-    fn debug_looks_correct() {
-        let parent = NamespaceSet::from("http://www.w3.org/2000/svg".to_owned());
-        let namespaces = NamespaceSet::from((
-            "xhtml".to_owned(),
-            "http://www.w3.org/1999/xhtml".to_owned(),
-        ));
-        namespaces.set_parent(Rc::new(parent));
-        assert_eq!(format!("{:?}", namespaces), "NamespaceSet(xmlns:xhtml=\"http://www.w3.org/1999/xhtml\", parent: Some(NamespaceSet(xmlns=\"http://www.w3.org/2000/svg\", parent: None)))");
-    }
-}

minidom-rs/src/namespaces.rs 🔗

@@ -0,0 +1,38 @@
+// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+// Copyright (c) 2020 Astro <astro@spaceboyz.net>
+// Copyright (c) 2020 Maxime “pep” Buquet <pep@bouah.net>
+// Copyright (c) 2020 Xidorn Quan <me@upsuper.org>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+/// Use to compare namespaces
+pub enum NSChoice<'a> {
+    /// The element must have no namespace
+    None,
+    /// The element's namespace must match the specified namespace
+    OneOf(&'a str),
+    /// The element's namespace must be in the specified vector
+    AnyOf(&'a [&'a str]),
+    /// The element can have any namespace, or no namespace
+    Any,
+}
+
+impl<'a> From<&'a str> for NSChoice<'a> {
+    fn from(ns: &'a str) -> NSChoice<'a> {
+        NSChoice::OneOf(ns)
+    }
+}
+
+impl<'a> NSChoice<'a> {
+    pub(crate) fn compare(&self, ns: &str) -> bool {
+        match (ns, &self) {
+            (_, NSChoice::None) => false,
+            (_, NSChoice::Any) => true,
+            (ns, NSChoice::OneOf(wanted_ns)) => &ns == wanted_ns,
+            (ns, NSChoice::AnyOf(wanted_nss)) => wanted_nss.iter().any(|w| &ns == w),
+        }
+    }
+}

minidom-rs/src/node.rs 🔗

@@ -12,6 +12,7 @@ use crate::element::{Element, ElementBuilder};
 use crate::error::Result;
 
 use std::io::Write;
+use std::collections::BTreeMap;
 
 use quick_xml::events::{BytesText, Event};
 use quick_xml::Writer as EventWriter;
@@ -34,7 +35,7 @@ impl Node {
     /// ```rust
     /// use minidom::Node;
     ///
-    /// let elm = Node::Element("<meow />".parse().unwrap());
+    /// let elm = Node::Element("<meow xmlns=\"ns1\"/>".parse().unwrap());
     /// let txt = Node::Text("meow".to_owned());
     ///
     /// assert_eq!(elm.as_element().unwrap().name(), "meow");
@@ -55,7 +56,7 @@ impl Node {
     /// ```rust
     /// use minidom::Node;
     ///
-    /// let mut elm = Node::Element("<meow />".parse().unwrap());
+    /// let mut elm = Node::Element("<meow xmlns=\"ns1\"/>".parse().unwrap());
     /// let mut txt = Node::Text("meow".to_owned());
     ///
     /// assert_eq!(elm.as_element_mut().unwrap().name(), "meow");
@@ -76,7 +77,7 @@ impl Node {
     /// ```rust
     /// use minidom::Node;
     ///
-    /// let elm = Node::Element("<meow />".parse().unwrap());
+    /// let elm = Node::Element("<meow xmlns=\"ns1\"/>".parse().unwrap());
     /// let txt = Node::Text("meow".to_owned());
     ///
     /// assert_eq!(elm.into_element().unwrap().name(), "meow");
@@ -97,7 +98,7 @@ impl Node {
     /// ```rust
     /// use minidom::Node;
     ///
-    /// let elm = Node::Element("<meow />".parse().unwrap());
+    /// let elm = Node::Element("<meow xmlns=\"ns1\"/>".parse().unwrap());
     /// let txt = Node::Text("meow".to_owned());
     ///
     /// assert_eq!(elm.as_text(), None);
@@ -118,7 +119,7 @@ impl Node {
     /// ```rust
     /// use minidom::Node;
     ///
-    /// let mut elm = Node::Element("<meow />".parse().unwrap());
+    /// let mut elm = Node::Element("<meow xmlns=\"ns1\"/>".parse().unwrap());
     /// let mut txt = Node::Text("meow".to_owned());
     ///
     /// assert_eq!(elm.as_text_mut(), None);
@@ -145,7 +146,7 @@ impl Node {
     /// ```rust
     /// use minidom::Node;
     ///
-    /// let elm = Node::Element("<meow />".parse().unwrap());
+    /// let elm = Node::Element("<meow xmlns=\"ns1\"/>".parse().unwrap());
     /// let txt = Node::Text("meow".to_owned());
     ///
     /// assert_eq!(elm.into_text(), None);
@@ -159,9 +160,9 @@ impl Node {
     }
 
     #[doc(hidden)]
-    pub(crate) fn write_to_inner<W: Write>(&self, writer: &mut EventWriter<W>) -> Result<()> {
+    pub(crate) fn write_to_inner<W: Write>(&self, writer: &mut EventWriter<W>, prefixes: &mut BTreeMap<Option<String>, String>) -> Result<()> {
         match *self {
-            Node::Element(ref elmt) => elmt.write_to_inner(writer)?,
+            Node::Element(ref elmt) => elmt.write_to_inner(writer, prefixes)?,
             Node::Text(ref s) => {
                 writer.write_event(Event::Text(BytesText::from_plain_str(s)))?;
             }

minidom-rs/src/parser.rs 🔗

@@ -0,0 +1,184 @@
+// Copyright (c) 2020 Maxime “pep” Buquet <pep@bouah.net>
+// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+//! Provides a `Parser` type, which takes bytes and returns Elements. It also keeps a hold of
+//! ascendant elements to be able to handle namespaces properly.
+
+use crate::element::Element;
+use crate::error::{Error, ParserError, Result};
+
+use bytes::BytesMut;
+use quick_xml::Reader as EventReader;
+use std::cell::RefCell;
+use std::str;
+
+/// Parser
+#[derive(Debug)]
+pub struct Parser {
+    buffer: RefCell<BytesMut>,
+    state: ParserState,
+}
+
+/// Describes the state of the parser.
+///
+/// This parser will only accept one-level documents. The root element is kept for convenience, to
+/// be able to pass namespaces down to children who are themselves children.
+#[derive(Debug)]
+pub enum ParserState {
+    /// Not enough data has been processed to find the first element.
+    Empty,
+
+    /// The normal state. the root element has been identified and children are processed.
+    Root {
+        /// Root element. Kept for future reference
+        root: Element,
+
+        /// Child element
+        child: Option<Element>,
+
+        /// XXX: Weird flag to say if we've already sent what we could send or if there's more to
+        /// send. This Variant needs to be changed.
+        sent: bool,
+    },
+
+    /// Something was passed in the buffer that made the parser get into an error state.
+    Error,
+
+    /// The root element has been closed. No feed-ing can happen past this point.
+    Closed,
+}
+
+/// Result of polling the parser
+#[derive(Debug)]
+pub enum ParserResult {
+    /// Buffer is not empty but needs more data
+    Partial,
+
+    /// An Element has been generated from the buffer.
+    Single(Element),
+}
+
+/*
+/// Split <stream:stream> and parse it.
+fn split_stream_stream_stream_features(string: String) -> (Element, Element) {
+    let mut stuff = string.splitn(2, '>');
+    let stream_opening_str = stuff.next().unwrap().to_string() + "/>";
+    let rest = stuff.next().unwrap().to_string();
+    let stream_opening: Element = stream_opening_str.parse().unwrap();
+    let rest: Element = rest.parse().unwrap();
+    println!("opening: {}", String::from(&stream_opening));
+    println!("features: {}", String::from(&rest));
+    (stream_opening, rest)
+}
+*/
+
+fn maybe_split_prolog(string: &str) -> &str {
+    if string.starts_with("<?xml") {
+        let mut stuff = string.splitn(2, '>');
+        stuff.next();
+        stuff.next().unwrap()
+    } else {
+        string
+    }
+}
+
+impl Parser {
+    /// Creates a new Parser
+    pub fn new() -> Parser {
+        Parser {
+            buffer: RefCell::new(BytesMut::new()),
+            state: ParserState::Empty,
+        }
+    }
+
+    /// Feed bytes to the parser.
+    pub fn feed(&mut self, bytes: BytesMut) -> Result<()> {
+        self.buffer.borrow_mut().unsplit(bytes);
+        let state = match self.state {
+            ParserState::Empty => {
+                // TODO: Try splitting xml prolog and stream header
+                let foo = self.buffer.borrow();
+                let header = maybe_split_prolog(str::from_utf8(foo.as_ref())?);
+                println!("FOO: header: {:?}", header);
+                let mut reader = EventReader::from_str(header);
+                let root = Element::from_reader(&mut reader);
+                match root {
+                    Ok(root) => {
+                        println!("FOO: elem: {:?}", root);
+                        ParserState::Root {
+                            root,
+                            child: None,
+                            sent: false,
+                        }
+                    }
+                    Err(e) => {
+                        println!("FOO: err: {:?}", e);
+                        ParserState::Empty
+                    }
+                }
+            }
+            ParserState::Closed => return Err(Error::ParserError(ParserError::Closed)),
+            _ => ParserState::Empty,
+        };
+
+        self.state = state;
+        Ok(())
+    }
+
+    /// Returns Elements to the application.
+    pub fn poll(&mut self) -> Result<Option<ParserResult>> {
+        match &self.state {
+            ParserState::Empty if self.buffer.borrow().len() != 0 => {
+                Ok(Some(ParserResult::Partial))
+            }
+            ParserState::Empty | ParserState::Closed | ParserState::Error => Ok(None),
+            ParserState::Root {
+                root, child: None, ..
+            } => Ok(Some(ParserResult::Single(root.clone()))),
+            ParserState::Root {
+                child: Some(child), ..
+            } => Ok(Some(ParserResult::Single(child.clone()))),
+        }
+    }
+
+    /// Resets the parser
+    pub fn reset(&mut self) {
+        *self = Parser::new();
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use bytes::{BufMut, BytesMut};
+
+    #[test]
+    fn test_prolog() {
+        let mut parser = Parser::new();
+        let mut buf = BytesMut::new();
+        buf.put(&b"<?xml version='1.0'?>"[..]);
+        buf.put(&b"<stream:stream xmlns='jabber:client' xml:lang='en' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='foo.bar'>"[..]);
+        match parser.feed(buf) {
+            Ok(_) => (),
+            _ => panic!(),
+        }
+
+        let elem = Element::builder("stream:stream", "http://etherx.jabber.org/streams")
+            .prefix_ns(None, "jabber:client")
+            .attr("xml:lang", "en")
+            .attr("version", "1.0")
+            .attr("to", "foo.bar")
+            .build();
+
+        println!("BAR: elem: {:?}", elem);
+
+        match parser.poll() {
+            Ok(Some(ParserResult::Single(e))) => assert_eq!(e, elem),
+            _ => panic!(),
+        }
+    }
+}

minidom-rs/src/prefixes.rs 🔗

@@ -0,0 +1,96 @@
+// Copyright (c) 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+// Copyright (c) 2020 Astro <astro@spaceboyz.net>
+// Copyright (c) 2020 Maxime “pep” Buquet <pep@bouah.net>
+// Copyright (c) 2020 Xidorn Quan <me@upsuper.org>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use std::collections::BTreeMap;
+use std::fmt;
+
+#[derive(Clone, PartialEq, Eq)]
+pub struct Prefixes {
+    prefixes: BTreeMap<String, Option<String>>,
+}
+
+impl Default for Prefixes {
+    fn default() -> Self {
+        Prefixes {
+            prefixes: BTreeMap::new(),
+        }
+    }
+}
+
+impl fmt::Debug for Prefixes {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "Prefixes(")?;
+        for (namespace, prefix) in &self.prefixes {
+            write!(
+                f,
+                "xmlns{}={:?} ",
+                match prefix {
+                    None => String::new(),
+                    Some(prefix) => format!(":{}", prefix),
+                },
+                namespace
+            )?;
+        }
+        write!(f, ")")
+    }
+}
+
+impl Prefixes {
+    pub fn declared_prefixes(&self) -> &BTreeMap<String, Option<String>> {
+        &self.prefixes
+    }
+
+    pub fn get(&self, namespace: &String) -> Option<Option<String>> {
+        match self.prefixes.get(namespace) {
+            Some(ns) => Some(ns.clone()),
+            None => None,
+        }
+    }
+}
+
+impl From<BTreeMap<String, Option<String>>> for Prefixes {
+    fn from(prefixes: BTreeMap<String, Option<String>>) -> Self {
+        Prefixes { prefixes }
+    }
+}
+
+impl From<Option<String>> for Prefixes {
+    fn from(namespace: Option<String>) -> Self {
+        match namespace {
+            None => Self::default(),
+            Some(namespace) => Self::from(namespace),
+        }
+    }
+}
+
+impl From<String> for Prefixes {
+    fn from(namespace: String) -> Self {
+        let mut prefixes = BTreeMap::new();
+        prefixes.insert(namespace, None);
+
+        Prefixes { prefixes }
+    }
+}
+
+impl From<(Option<String>, String)> for Prefixes {
+    fn from(prefix_namespace: (Option<String>, String)) -> Self {
+        let (prefix, namespace) = prefix_namespace;
+        let mut prefixes = BTreeMap::new();
+        prefixes.insert(namespace, prefix);
+
+        Prefixes { prefixes }
+    }
+}
+
+impl From<(String, String)> for Prefixes {
+    fn from(prefix_namespace: (String, String)) -> Self {
+        let (prefix, namespace) = prefix_namespace;
+        Self::from((Some(prefix), namespace))
+    }
+}

minidom-rs/src/tests.rs 🔗

@@ -18,16 +18,14 @@ use quick_xml::Reader;
 const TEST_STRING: &'static str = r#"<root xmlns="root_ns" a="b" xml:lang="en">meow<child c="d"/><child xmlns="child_ns" d="e" xml:lang="fr"/>nya</root>"#;
 
 fn build_test_tree() -> Element {
-    let mut root = Element::builder("root")
-        .ns("root_ns")
+    let mut root = Element::builder("root", "root_ns")
         .attr("xml:lang", "en")
         .attr("a", "b")
         .build();
     root.append_text_node("meow");
-    let child = Element::builder("child").attr("c", "d").build();
+    let child = Element::builder("child", "root_ns").attr("c", "d").build();
     root.append_child(child);
-    let other_child = Element::builder("child")
-        .ns("child_ns")
+    let other_child = Element::builder("child", "child_ns")
         .attr("d", "e")
         .attr("xml:lang", "fr")
         .build();
@@ -45,6 +43,80 @@ fn reader_works() {
     );
 }
 
+#[test]
+fn reader_deduplicate_prefixes() {
+    // The reader shouldn't complain that "child" doesn't have a namespace. It should reuse the
+    // parent ns with the same prefix.
+    let _: Element = r#"<root xmlns="ns1"><child/></root>"#.parse().unwrap();
+    let _: Element = r#"<p1:root xmlns:p1="ns1"><p1:child/></p1:root>"#.parse().unwrap();
+    let _: Element = r#"<root xmlns="ns1"><child xmlns:p1="ns2"><p1:grandchild/></child></root>"#.parse().unwrap();
+
+    match r#"<p1:root xmlns:p1="ns1"><child/></p1:root>"#.parse::<Element>() {
+        Err(Error::MissingNamespace) => (),
+        Err(err) => panic!("No or wrong error: {:?}", err),
+        Ok(elem) => panic!("Got Element: {}; was expecting Error::MissingNamespace", String::from(&elem)),
+    }
+}
+
+#[test]
+fn reader_no_deduplicate_sibling_prefixes() {
+    // The reader shouldn't reuse the sibling's prefixes
+    match r#"<root xmlns="ns1"><p1:child1 xmlns:p1="ns2"/><p1:child2/></root>"#.parse::<Element>() {
+        Err(Error::MissingNamespace) => (),
+        Err(err) => panic!("No or wrong error: {:?}", err),
+        Ok(elem) => panic!("Got Element:\n{:?}\n{}\n; was expecting Error::MissingNamespace", elem, String::from(&elem)),
+    }
+}
+
+#[test]
+fn test_real_data() {
+    let correction = Element::builder("replace", "urn:xmpp:message-correct:0").build();
+    let body = Element::builder("body", "jabber:client").build();
+    let message = Element::builder("message", "jabber:client")
+        .append(body)
+        .append(correction)
+        .build();
+    let stream = Element::builder("stream", "http://etherx.jabber.org/streams")
+        .prefix(Some(String::from("stream")), "http://etherx.jabber.org/streams")
+        .prefix(None, "jabber:client")
+        .append(message)
+        .build();
+    println!("{}", String::from(&stream));
+
+    let jid = Element::builder("jid", "urn:xmpp:presence:0").build();
+    let nick = Element::builder("nick", "urn:xmpp:presence:0").build();
+    let mix = Element::builder("mix", "urn:xmpp:presence:0")
+        .append(jid)
+        .append(nick)
+        .build();
+    let show = Element::builder("show", "jabber:client").build();
+    let status = Element::builder("status", "jabber:client").build();
+    let presence = Element::builder("presence", "jabber:client")
+        .append(show)
+        .append(status)
+        .append(mix)
+        .build();
+    let item = Element::builder("item", "http://jabber.org/protocol/pubsub")
+        .append(presence)
+        .build();
+    let items = Element::builder("items", "http://jabber.org/protocol/pubsub")
+        .append(item)
+        .build();
+    let pubsub = Element::builder("pubsub", "http://jabber.org/protocol/pubsub")
+        .append(items)
+        .build();
+    let iq = Element::builder("iq", "jabber:client")
+        .append(pubsub)
+        .build();
+    let stream = Element::builder("stream", "http://etherx.jabber.org/streams")
+        .prefix(Some(String::from("stream")), "http://etherx.jabber.org/streams")
+        .prefix(None, "jabber:client")
+        .append(iq)
+        .build();
+
+    println!("{}", String::from(&stream));
+}
+
 #[test]
 fn writer_works() {
     let root = build_test_tree();
@@ -66,39 +138,115 @@ fn writer_with_decl_works() {
     assert_eq!(String::from_utf8(writer).unwrap(), result);
 }
 
+#[test]
+fn writer_with_prefix() {
+    let root = Element::builder("root", "ns1")
+        .prefix(Some(String::from("p1")), "ns1")
+        .prefix(None, "ns2")
+        .build();
+    assert_eq!(String::from(&root),
+        r#"<p1:root xmlns:p1="ns1" xmlns="ns2"/>"#,
+    );
+}
+
+#[test]
+fn writer_no_prefix_namespace() {
+    let root = Element::builder("root", "ns1").build();
+    // TODO: Note that this isn't exactly equal to a None prefix. it's just that the None prefix is
+    // the most obvious when it's not already used. Maybe fix tests so that it only checks that the
+    // prefix used equals the one declared for the namespace.
+    assert_eq!(String::from(&root), r#"<root xmlns="ns1"/>"#);
+}
+
+#[test]
+fn writer_no_prefix_namespace_child() {
+    let child = Element::builder("child", "ns1").build();
+    let root = Element::builder("root", "ns1")
+        .append(child)
+        .build();
+    // TODO: Same remark as `writer_no_prefix_namespace`.
+    assert_eq!(String::from(&root), r#"<root xmlns="ns1"><child/></root>"#);
+
+    let child = Element::builder("child", "ns2")
+        .prefix(None, "ns3")
+        .build();
+    let root = Element::builder("root", "ns1")
+        .append(child)
+        .build();
+    // TODO: Same remark as `writer_no_prefix_namespace`.
+    assert_eq!(String::from(&root), r#"<root xmlns="ns1"><ns0:child xmlns:ns0="ns2" xmlns="ns3"/></root>"#);
+}
+
+#[test]
+fn writer_prefix_namespace_child() {
+    let child = Element::builder("child", "ns1").build();
+    let root = Element::builder("root", "ns1")
+        .prefix(Some(String::from("p1")), "ns1")
+        .append(child)
+        .build();
+    assert_eq!(String::from(&root), r#"<p1:root xmlns:p1="ns1"><p1:child/></p1:root>"#);
+}
+
+#[test]
+fn writer_with_prefix_deduplicate() {
+    let child = Element::builder("child", "ns1")
+        // .prefix(Some(String::from("p1")), "ns1")
+        .build();
+    let root = Element::builder("root", "ns1")
+        .prefix(Some(String::from("p1")), "ns1")
+        .prefix(None, "ns2")
+        .append(child)
+        .build();
+    assert_eq!(String::from(&root),
+        r#"<p1:root xmlns:p1="ns1" xmlns="ns2"><p1:child/></p1:root>"#,
+    );
+
+    // Ensure descendants don't just reuse ancestors' prefixes that have been shadowed in between
+    let grandchild = Element::builder("grandchild", "ns1")
+        .build();
+    let child = Element::builder("child", "ns2")
+        .append(grandchild)
+        .build();
+    let root = Element::builder("root", "ns1")
+        .append(child)
+        .build();
+    assert_eq!(String::from(&root),
+        r#"<root xmlns="ns1"><child xmlns="ns2"><grandchild xmlns="ns1"/></child></root>"#,
+    );
+}
+
 #[test]
 fn writer_escapes_attributes() {
-    let root = Element::builder("root").attr("a", "\"Air\" quotes").build();
+    let root = Element::builder("root", "ns1").attr("a", "\"Air\" quotes").build();
     let mut writer = Vec::new();
     {
         root.write_to(&mut writer).unwrap();
     }
     assert_eq!(
         String::from_utf8(writer).unwrap(),
-        r#"<root a="&quot;Air&quot; quotes"/>"#
+        r#"<root xmlns="ns1" a="&quot;Air&quot; quotes"/>"#
     );
 }
 
 #[test]
 fn writer_escapes_text() {
-    let root = Element::builder("root").append("<3").build();
+    let root = Element::builder("root", "ns1").append("<3").build();
     let mut writer = Vec::new();
     {
         root.write_to(&mut writer).unwrap();
     }
-    assert_eq!(String::from_utf8(writer).unwrap(), r#"<root>&lt;3</root>"#);
+    assert_eq!(String::from_utf8(writer).unwrap(), r#"<root xmlns="ns1">&lt;3</root>"#);
 }
 
 #[test]
 fn builder_works() {
-    let elem = Element::builder("a")
-        .ns("b")
+    let elem = Element::builder("a", "b")
         .attr("c", "d")
-        .append(Element::builder("child"))
+        .append(Element::builder("child", "b"))
         .append("e")
         .build();
     assert_eq!(elem.name(), "a");
-    assert_eq!(elem.ns(), Some("b".to_owned()));
+    assert_eq!(elem.ns(), "b".to_owned());
     assert_eq!(elem.attr("c"), Some("d"));
     assert_eq!(elem.attr("x"), None);
     assert_eq!(elem.text(), "e");
@@ -140,9 +288,9 @@ fn get_child_works() {
 
 #[test]
 fn namespace_propagation_works() {
-    let mut root = Element::builder("root").ns("root_ns").build();
-    let mut child = Element::bare("child");
-    let grandchild = Element::bare("grandchild");
+    let mut root = Element::builder("root", "root_ns").build();
+    let mut child = Element::bare("child", "root_ns");
+    let grandchild = Element::bare("grandchild", "root_ns");
     child.append_child(grandchild);
     root.append_child(child);
 
@@ -159,12 +307,12 @@ fn namespace_propagation_works() {
 
 #[test]
 fn two_elements_with_same_arguments_different_order_are_equal() {
-    let elem1: Element = "<a b='a' c=''/>".parse().unwrap();
-    let elem2: Element = "<a c='' b='a'/>".parse().unwrap();
+    let elem1: Element = "<a b='a' c='' xmlns='ns1'/>".parse().unwrap();
+    let elem2: Element = "<a c='' b='a' xmlns='ns1'/>".parse().unwrap();
     assert_eq!(elem1, elem2);
 
-    let elem1: Element = "<a b='a' c=''/>".parse().unwrap();
-    let elem2: Element = "<a c='d' b='a'/>".parse().unwrap();
+    let elem1: Element = "<a b='a' c='' xmlns='ns1'/>".parse().unwrap();
+    let elem2: Element = "<a c='d' b='a' xmlns='ns1'/>".parse().unwrap();
     assert_ne!(elem1, elem2);
 }
 
@@ -184,11 +332,11 @@ fn namespace_attributes_works() {
 
 #[test]
 fn wrongly_closed_elements_error() {
-    let elem1 = "<a></b>".parse::<Element>();
+    let elem1 = "<a xmlns='ns1'></b>".parse::<Element>();
     assert!(elem1.is_err());
-    let elem1 = "<a></c></a>".parse::<Element>();
+    let elem1 = "<a xmlns='ns1'></c></a>".parse::<Element>();
     assert!(elem1.is_err());
-    let elem1 = "<a><c><d/></c></a>".parse::<Element>();
+    let elem1 = "<a xmlns='ns1'><c xmlns='ns1'><d xmlns='ns1'/></c></a>".parse::<Element>();
     assert!(elem1.is_ok());
 }
 
@@ -196,7 +344,7 @@ fn wrongly_closed_elements_error() {
 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()));
+    assert_eq!(elem.ns(), "jabber:client".to_owned());
 }
 
 #[test]
@@ -207,53 +355,53 @@ fn namespace_prefixed() {
     assert_eq!(elem.name(), "features");
     assert_eq!(
         elem.ns(),
-        Some("http://etherx.jabber.org/streams".to_owned())
+        "http://etherx.jabber.org/streams".to_owned(),
     );
 }
 
 #[test]
 fn namespace_inherited_simple() {
-    let elem: Element = "<stream xmlns='jabber:client'><message/></stream>"
+    let elem: Element = "<stream xmlns='jabber:client'><message xmlns='jabber:client' /></stream>"
         .parse()
         .unwrap();
     assert_eq!(elem.name(), "stream");
-    assert_eq!(elem.ns(), Some("jabber:client".to_owned()));
+    assert_eq!(elem.ns(), "jabber:client".to_owned());
     let child = elem.children().next().unwrap();
     assert_eq!(child.name(), "message");
-    assert_eq!(child.ns(), Some("jabber:client".to_owned()));
+    assert_eq!(child.ns(), "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>"
+    let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'><message xmlns='jabber:client' /></stream:features>"
         .parse().unwrap();
     assert_eq!(elem.name(), "features");
     assert_eq!(
         elem.ns(),
-        Some("http://etherx.jabber.org/streams".to_owned())
+        "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()));
+    assert_eq!(child.ns(), "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>"
+    let elem: Element = "<stream xmlns='http://etherx.jabber.org/streams' xmlns:jabber='jabber:client'><jabber:message xmlns:jabber='jabber:client' /></stream>"
         .parse().unwrap();
     assert_eq!(elem.name(), "stream");
     assert_eq!(
         elem.ns(),
-        Some("http://etherx.jabber.org/streams".to_owned())
+        "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()));
+    assert_eq!(child.ns(), "jabber:client".to_owned());
 }
 
 #[test]
 fn fail_comments() {
-    let elem: Result<Element, Error> = "<foo><!-- bar --></foo>".parse();
+    let elem: Result<Element, Error> = "<foo xmlns='ns1'><!-- bar --></foo>".parse();
     match elem {
         Err(Error::NoComments) => (),
         _ => panic!(),
@@ -262,12 +410,12 @@ fn fail_comments() {
 
 #[test]
 fn xml_error() {
-    match "<a></b>".parse::<Element>() {
+    match "<a xmlns='ns1'></b>".parse::<Element>() {
         Err(crate::error::Error::XmlError(_)) => (),
         err => panic!("No or wrong error: {:?}", err),
     }
 
-    match "<a></".parse::<Element>() {
+    match "<a xmlns='ns1'></".parse::<Element>() {
         Err(crate::error::Error::XmlError(_)) => (),
         err => panic!("No or wrong error: {:?}", err),
     }
@@ -280,3 +428,11 @@ fn invalid_element_error() {
         err => panic!("No or wrong error: {:?}", err),
     }
 }
+
+#[test]
+fn missing_namespace_error() {
+    match "<a/>".parse::<Element>() {
+        Err(crate::error::Error::MissingNamespace) => (),
+        err => panic!("No or wrong error: {:?}", err),
+    }
+}