Merge branch 'namespaceset' into 'master'

add NamespaceSet implementation

See merge request !14
This commit is contained in:
lumi 2017-08-13 18:01:13 +00:00
commit 90a5eb0d49
6 changed files with 305 additions and 78 deletions

View file

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

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::rc::Rc;
use std::borrow::Cow;
use error::{Error, ErrorKind, Result}; use error::{Error, ErrorKind, Result};
@ -17,6 +19,7 @@ use std::str::FromStr;
use std::slice; use std::slice;
use convert::{IntoElements, IntoAttributeValue, ElementEmitter}; use convert::{IntoElements, IntoAttributeValue, ElementEmitter};
use namespace_set::NamespaceSet;
/// Escape XML text /// Escape XML text
pub fn write_escaped<W: Write>(writer: &mut W, input: &str) -> Result<()> { pub fn write_escaped<W: Write>(writer: &mut W, input: &str) -> Result<()> {
@ -84,9 +87,9 @@ impl Node {
} }
} }
fn write_to_inner<W: Write>(&self, writer: &mut W, last_namespace: &mut Option<String>) -> Result<()>{ fn write_to_inner<W: Write>(&self, writer: &mut W) -> Result<()>{
match *self { 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)?,
} }
@ -97,8 +100,9 @@ impl Node {
#[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 +126,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 +149,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 +178,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 +192,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 +262,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 +328,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 => Cow::Borrowed(&self.name),
} &Some(ref prefix) => Cow::Owned(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.declared_ns() {
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 +369,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 +481,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.namespaces.clone());
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 +606,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 +751,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 +779,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.namespaces.clone());
}
}
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

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

150
src/namespace_set.rs Normal file
View file

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

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()
@ -149,3 +150,50 @@ fn wrongly_closed_elements_error() {
let elem1 = "<a><c><d/></c></a>".parse::<Element>(); let elem1 = "<a><c><d/></c></a>".parse::<Element>();
assert!(elem1.is_ok()); assert!(elem1.is_ok());
} }
#[test]
fn namespace_simple() {
let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
assert_eq!(elem.name(), "message");
assert_eq!(elem.ns(), Some("jabber:client".to_owned()));
}
#[test]
fn namespace_prefixed() {
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"
.parse().unwrap();
assert_eq!(elem.name(), "features");
assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams".to_owned()));
}
#[test]
fn namespace_inherited_simple() {
let elem: Element = "<stream xmlns='jabber:client'><message/></stream>".parse().unwrap();
assert_eq!(elem.name(), "stream");
assert_eq!(elem.ns(), Some("jabber:client".to_owned()));
let child = elem.children().next().unwrap();
assert_eq!(child.name(), "message");
assert_eq!(child.ns(), Some("jabber:client".to_owned()));
}
#[test]
fn namespace_inherited_prefixed1() {
let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'><message/></stream:features>"
.parse().unwrap();
assert_eq!(elem.name(), "features");
assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams".to_owned()));
let child = elem.children().next().unwrap();
assert_eq!(child.name(), "message");
assert_eq!(child.ns(), Some("jabber:client".to_owned()));
}
#[test]
fn namespace_inherited_prefixed2() {
let elem: Element = "<stream xmlns='http://etherx.jabber.org/streams' xmlns:jabber='jabber:client'><jabber:message/></stream>"
.parse().unwrap();
assert_eq!(elem.name(), "stream");
assert_eq!(elem.ns(), Some("http://etherx.jabber.org/streams".to_owned()));
let child = elem.children().next().unwrap();
assert_eq!(child.name(), "message");
assert_eq!(child.ns(), Some("jabber:client".to_owned()));
}