diff --git a/Cargo.toml b/Cargo.toml index ebb09d1..bb7a765 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "minidom" version = "0.5.0" -authors = ["lumi ", "Emmanuel Gil Peyrot ", "Bastien Orivel "] +authors = ["lumi ", "Emmanuel Gil Peyrot ", "Bastien Orivel ", "Astro "] 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" diff --git a/src/element.rs b/src/element.rs index db41fd5..e0aebf8 100644 --- a/src/element.rs +++ b/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(writer: &mut W, input: &str) -> Result<()> { @@ -84,9 +87,9 @@ impl Node { } } - fn write_to_inner(&self, writer: &mut W, last_namespace: &mut Option) -> Result<()>{ + fn write_to_inner(&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, name: String, - namespace: Option, + namespaces: Rc, attributes: BTreeMap, children: Vec, } @@ -122,10 +126,10 @@ impl FromStr for Element { } impl Element { - fn new(name: String, namespace: Option, attributes: BTreeMap, children: Vec) -> Element { + fn new>(name: String, prefix: Option, namespaces: NS, attributes: BTreeMap, children: Vec) -> 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>(name: S) -> ElementBuilder { + pub fn builder>(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>(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 { + 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, NS: AsRef>(&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(&self, writer: &mut W) -> Result<()> { - let mut last_namespace = None; write!(writer, "")?; - 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(&self, writer: &mut W, namespace: &str) -> Result<()> { - write!(writer, "")?; - self.write_to_inner(writer, &mut Some(namespace.to_owned())) - } + /// Like `write_to()` but without the `` prelude + pub fn write_to_inner(&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)?; - fn write_to_inner(&self, writer: &mut W, last_namespace: &mut Option) -> 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()); + 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 build_element(event: &BytesStart) -> Result { - 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::>>()?; - let mut ns_key = None; - for (key, _) in &attributes { - if key == "xmlns" || key.starts_with("xmlns:") { - ns_key = Some(key.clone()); - } - } +fn split_element_name>(s: S) -> Result<(Option, String)> { + let name_parts = s.as_ref().split(':').collect::>(); + 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), + } +} - 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())) +fn build_element(event: &BytesStart) -> Result { + 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::>>()?; + + 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, String>, } impl ElementBuilder { /// Sets the namespace. pub fn ns>(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); } diff --git a/src/error.rs b/src/error.rs index 0b8eb23..4079365 100644 --- a/src/error.rs +++ b/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") + } } } diff --git a/src/lib.rs b/src/lib.rs index 2e00b9f..b11fe06 100644 --- a/src/lib.rs +++ b/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; diff --git a/src/namespace_set.rs b/src/namespace_set.rs new file mode 100644 index 0000000..7671062 --- /dev/null +++ b/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>>, + namespaces: BTreeMap, String>, +} + +impl Default for NamespaceSet { + fn default() -> Self { + NamespaceSet { + parent: RefCell::new(None), + namespaces: BTreeMap::new(), + } + } +} + +impl NamespaceSet { + pub fn declared_ns(&self) -> &BTreeMap, String> { + &self.namespaces + } + + pub fn get(&self, prefix: &Option) -> Option { + 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>(&self, prefix: &Option, 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) { + let mut parent_ns = self.parent.borrow_mut(); + let new_set = parent; + *parent_ns = Some(new_set); + } + +} + +impl From, String>> for NamespaceSet { + fn from(namespaces: BTreeMap, String>) -> Self { + NamespaceSet { + parent: RefCell::new(None), + namespaces: namespaces, + } + } +} + +impl From> for NamespaceSet { + fn from(namespace: Option) -> Self { + match namespace { + None => Self::default(), + Some(namespace) => Self::from(namespace), + } + } +} + +impl From 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)> for NamespaceSet { + fn from(prefix_namespace: (Option, 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; + } + } + +} diff --git a/src/tests.rs b/src/tests.rs index 6dd6a2b..e787715 100644 --- a/src/tests.rs +++ b/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 = "".parse::(); assert!(elem1.is_ok()); } + +#[test] +fn namespace_simple() { + let elem: Element = "".parse().unwrap(); + assert_eq!(elem.name(), "message"); + assert_eq!(elem.ns(), Some("jabber:client".to_owned())); +} + +#[test] +fn namespace_prefixed() { + let elem: Element = "" + .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 = "".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 = "" + .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 = "" + .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())); +}