switch Element namespaces to NamespaceSet with parent reference

This commit is contained in:
Astro 2017-07-28 23:58:03 +02:00
commit a01cf553ae
3 changed files with 210 additions and 85 deletions

View file

@ -4,6 +4,8 @@ use std::io:: Write;
use std::collections::{btree_map, BTreeMap}; use std::collections::{btree_map, BTreeMap};
use std::str; use std::str;
use std::cell::RefCell;
use std::rc::Rc;
use error::{Error, ErrorKind, Result}; use error::{Error, ErrorKind, Result};
@ -84,9 +86,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 { 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)?, Node::Text(ref s) => write_escaped(writer, s)?,
} }
@ -94,11 +96,109 @@ impl Node {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq)]
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 {
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)
},
}
}
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),
},
}
}
fn set_parent(&self, parent: &Element) {
let mut parent_ns = self.parent.borrow_mut();
let new_set = ((&parent.namespaces) as &Rc<NamespaceSet>).clone();
*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))
}
}
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
/// A struct representing a DOM Element. /// A struct representing a DOM Element.
pub struct Element { pub struct Element {
prefix: Option<String>,
name: String, name: String,
namespace: Option<String>, namespaces: Rc<NamespaceSet>,
attributes: BTreeMap<String, String>, attributes: BTreeMap<String, String>,
children: Vec<Node>, children: Vec<Node>,
} }
@ -122,10 +222,10 @@ impl FromStr for Element {
} }
impl 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 { Element {
name: name, prefix, name,
namespace: namespace, namespaces: Rc::new(namespaces.into()),
attributes: attributes, attributes: attributes,
children: children, children: children,
} }
@ -145,14 +245,16 @@ impl Element {
/// .build(); /// .build();
/// ///
/// assert_eq!(elem.name(), "name"); /// 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("name"), Some("value"));
/// assert_eq!(elem.attr("inexistent"), None); /// assert_eq!(elem.attr("inexistent"), None);
/// assert_eq!(elem.text(), "inner"); /// 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 { 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 +274,9 @@ impl Element {
/// ``` /// ```
pub fn bare<S: Into<String>>(name: S) -> Element { pub fn bare<S: Into<String>>(name: S) -> Element {
Element { Element {
prefix: None,
name: name.into(), name: name.into(),
namespace: None, namespaces: Rc::new(NamespaceSet::default()),
attributes: BTreeMap::new(), attributes: BTreeMap::new(),
children: Vec::new(), children: Vec::new(),
} }
@ -185,9 +288,8 @@ impl Element {
} }
/// Returns a reference to the namespace of this element, if it has one, else `None`. /// Returns a reference to the namespace of this element, if it has one, else `None`.
pub fn ns(&self) -> Option<&str> { pub fn ns(&self) -> Option<String> {
self.namespace.as_ref() self.namespaces.get(&self.prefix)
.map(String::as_ref)
} }
/// Returns a reference to the value of the given attribute, if it exists, else `None`. /// Returns a reference to the value of the given attribute, if it exists, else `None`.
@ -256,8 +358,8 @@ impl Element {
/// assert_eq!(elem.is("wrong", "wrong"), false); /// assert_eq!(elem.is("wrong", "wrong"), false);
/// ``` /// ```
pub fn is<N: AsRef<str>, NS: AsRef<str>>(&self, name: N, namespace: NS) -> bool { 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() &&
self.name == name.as_ref() && ns == Some(namespace.as_ref()) self.namespaces.has(&self.prefix, namespace)
} }
/// Parse a document from an `EventReader`. /// Parse a document from an `EventReader`.
@ -322,27 +424,30 @@ impl Element {
/// Output a document to a `Writer`. /// Output a document to a `Writer`.
pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> { 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\"?>")?; 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 /// Like `write_to()` but without the `<?xml?>` prelude
pub fn write_to_in_namespace<W: Write>(&self, writer: &mut W, namespace: &str) -> Result<()> { pub fn write_to_inner<W: Write>(&self, writer: &mut W) -> Result<()> {
write!(writer, "<?xml version=\"1.0\" encoding=\"utf-8\"?>")?; let name = match &self.prefix {
self.write_to_inner(writer, &mut Some(namespace.to_owned())) &None => self.name.to_owned(),
} &Some(ref prefix) => format!("{}:{}", prefix, self.name),
};
write!(writer, "<{}", name)?;
fn write_to_inner<W: Write>(&self, writer: &mut W, last_namespace: &mut Option<String>) -> Result<()> { for (prefix, ns) in &self.namespaces.namespaces {
write!(writer, "<")?; match prefix {
write!(writer, "{}", self.name)?; &None => {
write!(writer, " xmlns=\"")?;
if let Some(ref ns) = self.namespace { write_escaped(writer, ns)?;
if *last_namespace != self.namespace { write!(writer, "\"")?;
write!(writer, " xmlns=\"")?; },
write_escaped(writer, ns)?; &Some(ref prefix) => {
write!(writer, "\"")?; write!(writer, " xmlns:{}=\"", prefix)?;
*last_namespace = Some(ns.clone()); write_escaped(writer, ns)?;
write!(writer, "\"")?;
},
} }
} }
@ -360,10 +465,10 @@ impl Element {
write!(writer, ">")?; write!(writer, ">")?;
for child in &self.children { 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(()) Ok(())
} }
@ -472,30 +577,17 @@ impl Element {
/// ///
/// assert_eq!(child.name(), "new"); /// assert_eq!(child.name(), "new");
/// ``` /// ```
pub fn append_child(&mut self, mut child: Element) -> &mut Element { pub fn append_child(&mut self, child: Element) -> &mut Element {
if child.namespace.is_none() && self.namespace.is_some() { child.namespaces.set_parent(&self);
child.namespace = self.namespace.clone();
child.propagate_namespaces();
}
self.children.push(Node::Element(child)); self.children.push(Node::Element(child));
if let Node::Element(ref mut cld) = *self.children.last_mut().unwrap() { if let Node::Element(ref mut cld) = *self.children.last_mut().unwrap() {
cld cld
} } else {
else {
unreachable!() 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`. /// Appends a text node to an `Element`.
/// ///
/// # Examples /// # Examples
@ -610,29 +702,42 @@ impl Element {
} }
} }
fn build_element(event: &BytesStart) -> Result<Element> { fn split_element_name<S: AsRef<str>>(s: S) -> Result<(Option<String>, String)> {
let mut attributes = event.attributes() let name_parts = s.as_ref().split(':').collect::<Vec<&str>>();
.map(|o| { match name_parts.len() {
let o = o?; 2 => Ok((Some(name_parts[0].to_owned()), name_parts[1].to_owned())),
let key = str::from_utf8(o.key)?.to_owned(); 1 => Ok((None, name_parts[0].to_owned())),
let value = str::from_utf8(o.value)?.to_owned(); _ => bail!(ErrorKind::InvalidElement),
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 ns = match ns_key { fn build_element(event: &BytesStart) -> Result<Element> {
None => None, let mut namespaces = BTreeMap::new();
Some(key) => attributes.remove(&key), let attributes = event.attributes()
}; .map(|o| {
let name = str::from_utf8(event.name())?.to_owned(); let o = o?;
Ok(Element::new(name, ns, attributes, Vec::new())) 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 (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`. /// An iterator over references to child elements of an `Element`.
@ -742,12 +847,14 @@ impl<'a> Iterator for AttrsMut<'a> {
/// A builder for `Element`s. /// A builder for `Element`s.
pub struct ElementBuilder { pub struct ElementBuilder {
root: Element, root: Element,
namespaces: BTreeMap<Option<String>, String>,
} }
impl ElementBuilder { impl ElementBuilder {
/// Sets the namespace. /// Sets the namespace.
pub fn ns<S: Into<String>>(mut self, namespace: S) -> ElementBuilder { 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 self
} }
@ -768,21 +875,33 @@ impl ElementBuilder {
/// Builds the `Element`. /// Builds the `Element`.
pub fn build(self) -> 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);
}
}
element
} }
} }
#[cfg(test)]
#[test] #[test]
fn test_element_new() { fn test_element_new() {
use std::iter::FromIterator; use std::iter::FromIterator;
let elem = Element::new( "name".to_owned() let elem = Element::new( "name".to_owned()
, None
, Some("namespace".to_owned()) , Some("namespace".to_owned())
, BTreeMap::from_iter(vec![ ("name".to_string(), "value".to_string()) ].into_iter() ) , BTreeMap::from_iter(vec![ ("name".to_string(), "value".to_string()) ].into_iter() )
, Vec::new() ); , Vec::new() );
assert_eq!(elem.name(), "name"); 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("name"), Some("value"));
assert_eq!(elem.attr("inexistent"), None); assert_eq!(elem.attr("inexistent"), None);
} }

View file

@ -26,5 +26,10 @@ error_chain! {
description("The XML is invalid, an element was wrongly closed") description("The XML is invalid, an element was wrongly closed")
display("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")
}
} }
} }

View file

@ -80,7 +80,7 @@ fn builder_works() {
.append("e") .append("e")
.build(); .build();
assert_eq!(elem.name(), "a"); 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("c"), Some("d"));
assert_eq!(elem.attr("x"), None); assert_eq!(elem.attr("x"), None);
assert_eq!(elem.text(), "e"); assert_eq!(elem.text(), "e");
@ -115,6 +115,7 @@ fn namespace_propagation_works() {
let grandchild = Element::bare("grandchild"); let grandchild = Element::bare("grandchild");
child.append_child(grandchild); child.append_child(grandchild);
root.append_child(child); 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().ns(), root.ns());
assert_eq!(root.get_child("child", "root_ns").unwrap() assert_eq!(root.get_child("child", "root_ns").unwrap()
.get_child("grandchild", "root_ns").unwrap() .get_child("grandchild", "root_ns").unwrap()
@ -154,7 +155,7 @@ fn wrongly_closed_elements_error() {
fn namespace_simple() { fn namespace_simple() {
let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap(); let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
assert_eq!(elem.name(), "message"); assert_eq!(elem.name(), "message");
assert_eq!(elem.ns(), Some("jabber:client")); assert_eq!(elem.ns(), Some("jabber:client".to_owned()));
} }
#[test] #[test]
@ -162,28 +163,28 @@ fn namespace_prefixed() {
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>" let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"
.parse().unwrap(); .parse().unwrap();
assert_eq!(elem.name(), "features"); assert_eq!(elem.name(), "features");
assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams")); assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams".to_owned()));
} }
#[test] #[test]
fn namespace_inherited_simple() { fn namespace_inherited_simple() {
let elem: Element = "<stream xmlns='jabber:client'><message/></stream>".parse().unwrap(); let elem: Element = "<stream xmlns='jabber:client'><message/></stream>".parse().unwrap();
assert_eq!(elem.name(), "stream"); assert_eq!(elem.name(), "stream");
assert_eq!(elem.ns(), Some("jabber:client")); assert_eq!(elem.ns(), Some("jabber:client".to_owned()));
let child = elem.children().next().unwrap(); let child = elem.children().next().unwrap();
assert_eq!(child.name(), "message"); assert_eq!(child.name(), "message");
assert_eq!(child.ns(), Some("jabber:client")); assert_eq!(child.ns(), Some("jabber:client".to_owned()));
} }
#[test] #[test]
fn namespace_inherited_prefixed1() { fn namespace_inherited_prefixed1() {
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'><message/></stream>" let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'><message/></stream:features>"
.parse().unwrap(); .parse().unwrap();
assert_eq!(elem.name(), "features"); assert_eq!(elem.name(), "features");
assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams")); assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams".to_owned()));
let child = elem.children().next().unwrap(); let child = elem.children().next().unwrap();
assert_eq!(child.name(), "message"); assert_eq!(child.name(), "message");
assert_eq!(child.ns(), Some("jabber:client")); assert_eq!(child.ns(), Some("jabber:client".to_owned()));
} }
#[test] #[test]
@ -191,8 +192,8 @@ 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/></stream>"
.parse().unwrap(); .parse().unwrap();
assert_eq!(elem.name(), "stream"); assert_eq!(elem.name(), "stream");
assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams")); assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams".to_owned()));
let child = elem.children().next().unwrap(); let child = elem.children().next().unwrap();
assert_eq!(child.name(), "message"); assert_eq!(child.name(), "message");
assert_eq!(child.ns(), Some("jabber:client")); assert_eq!(child.ns(), Some("jabber:client".to_owned()));
} }