diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cb292343052f44cf4efaa6f5bd686defc527ae8a..bd8cf4313765d2f38bea2ff4edc4a22ba61b9866 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,6 @@ -image: "scorpil/rust:stable" +image: "scorpil/rust:nightly" test:cargo: script: - rustc --version && cargo --version - - cargo test --verbose --jobs 1 --release \ No newline at end of file + - cargo test --verbose --jobs 1 --release diff --git a/src/attribute.rs b/src/attribute.rs index 5d45f40fad0f5ec77be5705cc370a2d6630ea051..57373a06dc7ee6b6a62b9b226edc0911dc1e22f4 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -1,3 +1,5 @@ +//! Provides an `Attribute` type which represents an attribute in an XML document. + use xml::escape::escape_str_attribute; use std::fmt; diff --git a/src/convert.rs b/src/convert.rs new file mode 100644 index 0000000000000000000000000000000000000000..83c187713fd98177a8a998a563e7ad928464a8ec --- /dev/null +++ b/src/convert.rs @@ -0,0 +1,101 @@ +//! A module which exports a few traits for converting types to elements and attributes. + +use element::{Element, ElementBuilder}; + +/// A struct which is used for emitting `Element`s and text nodes into an `Element` without +/// exposing more functionality than necessary. +pub struct ElementEmitter<'a>(&'a mut Element); + +impl<'a> ElementEmitter<'a> { + /// Creates a new `ElementEmitter`. + pub fn new(root: &'a mut Element) -> ElementEmitter<'a> { + ElementEmitter(root) + } + + /// Appends an `Element` to the target. + pub fn append_child(&mut self, element: Element) { + self.0.append_child(element); + } + + /// Appends a text node to the target. + pub fn append_text_node(&mut self, text: String) { + self.0.append_text_node(text); + } +} + +/// A trait for types which can be converted to one or multiple `Element`s. +pub trait IntoElements { + /// Emits this as a sequence of text nodes and `Element`s. + fn into_elements(self, emitter: &mut ElementEmitter); +} + +impl IntoElements for Vec { + fn into_elements(self, emitter: &mut ElementEmitter) { + for elem in self { + elem.into_elements(emitter); + } + } +} + +impl<'a, T: IntoElements + Clone> IntoElements for &'a [T] { + fn into_elements(self, emitter: &mut ElementEmitter) { + self.to_vec().into_elements(emitter); + } +} + +impl IntoElements for Option { + fn into_elements(self, emitter: &mut ElementEmitter) { + match self { + Some(e) => e.into_elements(emitter), + None => (), + } + } +} + +impl IntoElements for Element { + fn into_elements(self, emitter: &mut ElementEmitter) { + emitter.append_child(self); + } +} + +impl IntoElements for ElementBuilder { + fn into_elements(self, emitter: &mut ElementEmitter) { + emitter.append_child(self.build()); + } +} + +impl IntoElements for String { + fn into_elements(self, emitter: &mut ElementEmitter) { + emitter.append_text_node(self); + } +} + +impl<'a> IntoElements for &'a str { + fn into_elements(self, emitter: &mut ElementEmitter) { + emitter.append_text_node(self.to_owned()); + } +} + +/// A trait for types which can be converted to an attribute value. +pub trait IntoAttributeValue { + /// Turns this into an attribute string, or None if it shouldn't be added. + fn into_attribute_value(self) -> Option; +} + +impl IntoAttributeValue for String { + fn into_attribute_value(self) -> Option { + Some(self.clone()) + } +} + +impl<'a> IntoAttributeValue for &'a str { + fn into_attribute_value(self) -> Option { + Some((*self).to_owned()) + } +} + +impl IntoAttributeValue for Option { + fn into_attribute_value(self) -> Option { + self.and_then(|t| t.into_attribute_value()) + } +} diff --git a/src/element.rs b/src/element.rs new file mode 100644 index 0000000000000000000000000000000000000000..cedb90e5d9ede927ef116369fb2c8aca0f4d7bd3 --- /dev/null +++ b/src/element.rs @@ -0,0 +1,558 @@ +//! Provides an `Element` type, which represents DOM nodes, and a builder to create them with. + +use std::io::prelude::*; +use std::io::Cursor; + +use std::fmt; + +use error::Error; + +use attribute::Attribute; + +use xml::reader::{XmlEvent as ReaderEvent, EventReader}; +use xml::writer::{XmlEvent as WriterEvent, EventWriter}; +use xml::name::Name; +use xml::namespace::NS_NO_PREFIX; + +use std::str::FromStr; + +use std::slice; + +use convert::{IntoElements, IntoAttributeValue, ElementEmitter}; + +#[derive(Clone, PartialEq, Eq)] +/// A struct representing a DOM Element. +pub struct Element { + name: String, + namespace: Option, + attributes: Vec, + children: Vec, +} + +impl fmt::Debug for Element { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + if let Some(ref ns) = self.namespace { + write!(fmt, "<{{{}}}{}", ns, self.name)?; + } + else { + write!(fmt, "<{}", self.name)?; + } + for attr in &self.attributes { + write!(fmt, " {}", attr)?; + } + write!(fmt, ">")?; + for child in &self.children { + match *child { + Node::Element(ref e) => { + write!(fmt, "{:?}", e)?; + }, + Node::Text(ref s) => { + write!(fmt, "{}", s)?; + }, + } + } + write!(fmt, "", self.name)?; + Ok(()) + } +} + +impl FromStr for Element { + type Err = Error; + + fn from_str(s: &str) -> Result { + let mut reader = EventReader::new(Cursor::new(s)); + Element::from_reader(&mut reader) + } +} + +/// A node in an element tree. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Node { + /// An `Element`. + Element(Element), + /// A text node. + Text(String), +} + +impl Element { + fn new(name: String, namespace: Option, attributes: Vec, children: Vec) -> Element { + Element { + name: name, + namespace: namespace, + attributes: attributes, + children: children, + } + } + + /// Return a builder for an `Element` with the given `name`. + /// + /// # Examples + /// + /// ``` + /// use minidom::Element; + /// + /// let elem = Element::builder("name") + /// .ns("namespace") + /// .attr("name", "value") + /// .append("inner") + /// .build(); + /// + /// assert_eq!(elem.name(), "name"); + /// assert_eq!(elem.ns(), Some("namespace")); + /// assert_eq!(elem.attr("name"), Some("value")); + /// assert_eq!(elem.attr("inexistent"), None); + /// assert_eq!(elem.text(), "inner"); + /// ``` + pub fn builder>(name: S) -> ElementBuilder { + ElementBuilder { + root: Element::new(name.into(), None, Vec::new(), Vec::new()), + } + } + + /// Returns a bare minimum `Element` with this name. + /// + /// # Examples + /// + /// ``` + /// use minidom::Element; + /// + /// let bare = Element::bare("name"); + /// + /// assert_eq!(bare.name(), "name"); + /// assert_eq!(bare.ns(), None); + /// assert_eq!(bare.attr("name"), None); + /// assert_eq!(bare.text(), ""); + /// ``` + pub fn bare>(name: S) -> Element { + Element { + name: name.into(), + namespace: None, + attributes: Vec::new(), + children: Vec::new(), + } + } + + /// Returns a reference to the name of this element. + pub fn name(&self) -> &str { + &self.name + } + + /// 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) + } + + /// Returns a reference to the value of the given attribute, if it exists, else `None`. + pub fn attr(&self, name: &str) -> Option<&str> { + for attr in &self.attributes { + if attr.name == name { + return Some(&attr.value); + } + } + None + } + + /// Modifies the value of an attribute. + pub fn set_attr, V: IntoAttributeValue>(&mut self, name: S, val: V) { + let name = name.into(); + let val = val.into_attribute_value(); + for attr in &mut self.attributes { + if attr.name == name { + attr.value = val.expect("removing existing value via set_attr, this is not yet supported (TODO)"); // TODO + return; + } + } + if let Some(val) = val { + self.attributes.push(Attribute::new(name, val)); + } + } + + /// Returns whether the element has the given name and namespace. + /// + /// # Examples + /// + /// ``` + /// use minidom::Element; + /// + /// let elem = Element::builder("name").ns("namespace").build(); + /// + /// assert_eq!(elem.is("name", "namespace"), true); + /// assert_eq!(elem.is("name", "wrong"), false); + /// assert_eq!(elem.is("wrong", "namespace"), false); + /// 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()) + } + + /// Parse a document from an `EventReader`. + pub fn from_reader(reader: &mut EventReader) -> Result { + loop { + let e = reader.next()?; + match e { + ReaderEvent::StartElement { name, attributes, namespace } => { + let attributes = attributes.into_iter() + .map(|o| Attribute::new(o.name.local_name, o.value)) + .collect(); + let ns = if let Some(ref prefix) = name.prefix { + namespace.get(prefix) + } + else { + namespace.get(NS_NO_PREFIX) + }.map(|s| s.to_owned()); + let mut root = Element::new(name.local_name, ns, attributes, Vec::new()); + root.from_reader_inner(reader)?; + return Ok(root); + }, + ReaderEvent::EndDocument => { + return Err(Error::EndOfDocument); + }, + _ => () // TODO: may need more errors + } + } + } + + fn from_reader_inner(&mut self, reader: &mut EventReader) -> Result<(), Error> { + loop { + let e = reader.next()?; + match e { + ReaderEvent::StartElement { name, attributes, namespace } => { + let attributes = attributes.into_iter() + .map(|o| Attribute::new(o.name.local_name, o.value)) + .collect(); + let ns = if let Some(ref prefix) = name.prefix { + namespace.get(prefix) + } + else { + namespace.get(NS_NO_PREFIX) + }.map(|s| s.to_owned()); + let elem = Element::new(name.local_name, ns, attributes, Vec::with_capacity(1)); + let elem_ref = self.append_child(elem); + elem_ref.from_reader_inner(reader)?; + }, + ReaderEvent::EndElement { .. } => { + // TODO: may want to check whether we're closing the correct element + return Ok(()); + }, + ReaderEvent::Characters(s) => { + self.append_text_node(s); + }, + ReaderEvent::CData(s) => { + self.append_text_node(s); + }, + ReaderEvent::EndDocument => { + return Err(Error::EndOfDocument); + }, + _ => (), // TODO: may need to implement more + } + } + } + + /// Output a document to an `EventWriter`. + pub fn write_to(&self, writer: &mut EventWriter) -> Result<(), Error> { + let name = if let Some(ref ns) = self.namespace { + Name::qualified(&self.name, &ns, None) + } + else { + Name::local(&self.name) + }; + let mut start = WriterEvent::start_element(name); + if let Some(ref ns) = self.namespace { + start = start.default_ns(ns.as_ref()); + } + for attr in &self.attributes { // TODO: I think this could be done a lot more efficiently + start = start.attr(Name::local(&attr.name), &attr.value); + } + writer.write(start)?; + for child in &self.children { + match *child { + Node::Element(ref e) => { + e.write_to(writer)?; + }, + Node::Text(ref s) => { + writer.write(WriterEvent::characters(s))?; + }, + } + } + writer.write(WriterEvent::end_element())?; + Ok(()) + } + + /// Returns an iterator over references to the children of this element. + /// + /// # Examples + /// + /// ``` + /// use minidom::Element; + /// + /// let elem: Element = "".parse().unwrap(); + /// + /// let mut iter = elem.children(); + /// assert_eq!(iter.next().unwrap().name(), "child1"); + /// assert_eq!(iter.next().unwrap().name(), "child2"); + /// assert_eq!(iter.next().unwrap().name(), "child3"); + /// assert_eq!(iter.next(), None); + /// ``` + pub fn children<'a>(&'a self) -> Children<'a> { + Children { + iter: self.children.iter(), + } + } + + /// Returns an iterator over mutable references to the children of this element. + pub fn children_mut<'a>(&'a mut self) -> ChildrenMut<'a> { + ChildrenMut { + iter: self.children.iter_mut(), + } + } + + 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 child node to the `Element`, returning the appended node. + /// + /// # Examples + /// + /// ``` + /// use minidom::Element; + /// + /// let mut elem = Element::bare("root"); + /// + /// assert_eq!(elem.children().count(), 0); + /// + /// elem.append_child(Element::bare("child")); + /// + /// { + /// let mut iter = elem.children(); + /// assert_eq!(iter.next().unwrap().name(), "child"); + /// assert_eq!(iter.next(), None); + /// } + /// + /// let child = elem.append_child(Element::bare("new")); + /// + /// 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(); + } + self.children.push(Node::Element(child)); + if let Node::Element(ref mut cld) = *self.children.last_mut().unwrap() { + cld + } + else { + unreachable!() + } + } + + /// Appends a text node to an `Element`. + /// + /// # Examples + /// + /// ``` + /// use minidom::Element; + /// + /// let mut elem = Element::bare("node"); + /// + /// assert_eq!(elem.text(), ""); + /// + /// elem.append_text_node("text"); + /// + /// assert_eq!(elem.text(), "text"); + /// ``` + pub fn append_text_node>(&mut self, child: S) { + self.children.push(Node::Text(child.into())); + } + + /// Appends a node to an `Element`. + /// + /// # Examples + /// + /// ``` + /// use minidom::{Element, Node}; + /// + /// let mut elem = Element::bare("node"); + /// + /// elem.append_node(Node::Text("hello".to_owned())); + /// + /// assert_eq!(elem.text(), "hello"); + /// ``` + pub fn append_node(&mut self, node: Node) { + self.children.push(node); + } + + /// Returns the concatenation of all text nodes in the `Element`. + /// + /// # Examples + /// + /// ``` + /// use minidom::Element; + /// + /// let elem: Element = "hello, world!".parse().unwrap(); + /// + /// assert_eq!(elem.text(), "hello, world!"); + /// ``` + pub fn text(&self) -> String { + let mut ret = String::new(); + for fork in &self.children { + if let Node::Text(ref s) = *fork { + ret += s; + } + } + ret + } + + /// Returns a reference to the first child element with the specific name and namespace, if it + /// exists in the direct descendants of this `Element`, else returns `None`. + /// + /// # Examples + /// + /// ``` + /// use minidom::Element; + /// + /// let elem: Element = r#""#.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")); + /// assert_eq!(elem.get_child("c", "ns"), None); + /// assert_eq!(elem.get_child("b", "other_ns"), None); + /// assert_eq!(elem.get_child("a", "inexistent_ns"), None); + /// ``` + pub fn get_child, NS: AsRef>(&self, name: N, namespace: NS) -> Option<&Element> { + for fork in &self.children { + if let Node::Element(ref e) = *fork { + if e.is(name.as_ref(), namespace.as_ref()) { + return Some(e); + } + } + } + None + } + + /// Returns a mutable reference to the first child element with the specific name and namespace, + /// if it exists in the direct descendants of this `Element`, else returns `None`. + pub fn get_child_mut, NS: AsRef>(&mut self, name: N, namespace: NS) -> Option<&mut Element> { + for fork in &mut self.children { + if let Node::Element(ref mut e) = *fork { + if e.is(name.as_ref(), namespace.as_ref()) { + return Some(e); + } + } + } + None + } + + /// Returns whether a specific child with this name and namespace exists in the direct + /// descendants of the `Element`. + /// + /// # Examples + /// + /// ``` + /// use minidom::Element; + /// + /// let elem: Element = r#""#.parse().unwrap(); + /// + /// assert_eq!(elem.has_child("a", "other_ns"), true); + /// assert_eq!(elem.has_child("a", "ns"), true); + /// assert_eq!(elem.has_child("a", "inexistent_ns"), false); + /// assert_eq!(elem.has_child("b", "ns"), true); + /// assert_eq!(elem.has_child("b", "other_ns"), false); + /// assert_eq!(elem.has_child("b", "inexistent_ns"), false); + /// ``` + pub fn has_child, NS: AsRef>(&self, name: N, namespace: NS) -> bool { + self.get_child(name, namespace).is_some() + } +} + +/// An iterator over references to children of an `Element`. +pub struct Children<'a> { + iter: slice::Iter<'a, Node>, +} + +impl<'a> Iterator for Children<'a> { + type Item = &'a Element; + + fn next(&mut self) -> Option<&'a Element> { + while let Some(item) = self.iter.next() { + if let Node::Element(ref child) = *item { + return Some(child); + } + } + None + } +} + +/// An iterator over mutable references to children of an `Element`. +pub struct ChildrenMut<'a> { + iter: slice::IterMut<'a, Node>, +} + +impl<'a> Iterator for ChildrenMut<'a> { + type Item = &'a mut Element; + + fn next(&mut self) -> Option<&'a mut Element> { + while let Some(item) = self.iter.next() { + if let Node::Element(ref mut child) = *item { + return Some(child); + } + } + None + } +} + +/// A builder for `Element`s. +pub struct ElementBuilder { + root: Element, +} + +impl ElementBuilder { + /// Sets the namespace. + pub fn ns>(mut self, namespace: S) -> ElementBuilder { + self.root.namespace = Some(namespace.into()); + self + } + + /// Sets an attribute. + pub fn attr, V: IntoAttributeValue>(mut self, name: S, value: V) -> ElementBuilder { + self.root.set_attr(name, value); + self + } + + /// Appends anything implementing `IntoElements` into the tree. + pub fn append(mut self, into: T) -> ElementBuilder { + { + let mut emitter = ElementEmitter::new(&mut self.root); + into.into_elements(&mut emitter); + } + self + } + + /// Builds the `Element`. + pub fn build(self) -> Element { + self.root + } +} + +#[test] +fn test_element_new() { + let elem = Element::new( "name".to_owned() + , Some("namespace".to_owned()) + , vec![ Attribute::new("name", "value") ] + , Vec::new() ); + + assert_eq!(elem.name(), "name"); + assert_eq!(elem.ns(), Some("namespace")); + assert_eq!(elem.attr("name"), Some("value")); + assert_eq!(elem.attr("inexistent"), None); +} diff --git a/src/error.rs b/src/error.rs index 38f4069051b2de05338ac2ff08b8ff81394e54a2..dc818e2f651e8452d548182f5957b57663147aa1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +//! Provides an error type for this crate. + use std::io; use std::convert::From; @@ -8,9 +10,13 @@ use xml::reader::Error as ReaderError; /// An enum representing the possible errors. #[derive(Debug)] pub enum Error { + /// An io::Error. IoError(io::Error), + /// An error in the xml-rs `EventWriter`. XmlWriterError(WriterError), + /// An error in the xml-rs `EventReader`. XmlReaderError(ReaderError), + /// The end of the document has been reached unexpectedly. EndOfDocument, } diff --git a/src/lib.rs b/src/lib.rs index 9dfed4489b2e9282bc47e1648424e21b0394dd35..4728608db797f5ee40e62273acd5bffa58e38f8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![deny(missing_docs)] + //! A minimal DOM crate built on top of xml-rs. //! //! This library exports an `Element` struct which represents a DOM tree. @@ -64,622 +66,14 @@ extern crate xml; -mod error; - -mod attribute; - -use std::io::prelude::*; -use std::io::Cursor; - -use std::convert::AsRef; - -use std::iter::Iterator; - -use std::slice; - -use std::fmt; +pub mod error; +pub mod attribute; +pub mod element; +pub mod convert; -use std::str::FromStr; - -use xml::reader::{XmlEvent as ReaderEvent, EventReader}; -use xml::writer::{XmlEvent as WriterEvent, EventWriter}; -use xml::name::Name; -use xml::namespace::NS_NO_PREFIX; +#[cfg(test)] mod tests; pub use error::Error; - pub use attribute::Attribute; - -#[derive(Clone, PartialEq, Eq)] -/// A struct representing a DOM Element. -pub struct Element { - name: String, - namespace: Option, - attributes: Vec, - children: Vec, -} - -impl fmt::Debug for Element { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - if let Some(ref ns) = self.namespace { - write!(fmt, "<{{{}}}{}", ns, self.name)?; - } - else { - write!(fmt, "<{}", self.name)?; - } - for attr in &self.attributes { - write!(fmt, " {}", attr)?; - } - write!(fmt, ">")?; - for child in &self.children { - match *child { - Fork::Element(ref e) => { - write!(fmt, "{:?}", e)?; - }, - Fork::Text(ref s) => { - write!(fmt, "{}", s)?; - }, - } - } - write!(fmt, "", self.name)?; - Ok(()) - } -} - -impl FromStr for Element { - type Err = Error; - - fn from_str(s: &str) -> Result { - let mut reader = EventReader::new(Cursor::new(s)); - Element::from_reader(&mut reader) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -enum Fork { - Element(Element), - Text(String), -} - -impl Element { - /// Constructs a new `Element` with the given `name`, `namespace` and `attributes`. - /// - /// You probably should be using `Element::builder` instead of this. - /// - /// # Examples - /// - /// ``` - /// use minidom::{Element, Attribute}; - /// - /// let elem = Element::new( "name".to_owned() - /// , Some("namespace".to_owned()) - /// , vec![ Attribute::new("name", "value") ] ); - /// - /// assert_eq!(elem.name(), "name"); - /// assert_eq!(elem.ns(), Some("namespace")); - /// assert_eq!(elem.attr("name"), Some("value")); - /// assert_eq!(elem.attr("inexistent"), None); - /// ``` - pub fn new(name: String, namespace: Option, attributes: Vec) -> Element { - Element { - name: name, - namespace: namespace, - attributes: attributes, - children: Vec::new(), - } - } - - /// Return a builder for an `Element` with the given `name`. - /// - /// # Examples - /// - /// ``` - /// use minidom::Element; - /// - /// let elem = Element::builder("name") - /// .ns("namespace") - /// .attr("name", "value") - /// .text("inner") - /// .build(); - /// - /// assert_eq!(elem.name(), "name"); - /// assert_eq!(elem.ns(), Some("namespace")); - /// assert_eq!(elem.attr("name"), Some("value")); - /// assert_eq!(elem.attr("inexistent"), None); - /// assert_eq!(elem.text(), "inner"); - /// ``` - pub fn builder>(name: S) -> ElementBuilder { - ElementBuilder { - name: name.into(), - text: None, - namespace: None, - attributes: Vec::new(), - } - } - - /// Returns a bare minimum `Element` with this name. - /// - /// # Examples - /// - /// ``` - /// use minidom::Element; - /// - /// let bare = Element::bare("name"); - /// - /// assert_eq!(bare.name(), "name"); - /// assert_eq!(bare.ns(), None); - /// assert_eq!(bare.attr("name"), None); - /// assert_eq!(bare.text(), ""); - /// ``` - pub fn bare>(name: S) -> Element { - Element { - name: name.into(), - namespace: None, - attributes: Vec::new(), - children: Vec::new(), - } - } - - /// Returns a reference to the name of this element. - pub fn name(&self) -> &str { - &self.name - } - - /// 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) - } - - /// Returns a reference to the value of the given attribute, if it exists, else `None`. - pub fn attr(&self, name: &str) -> Option<&str> { - for attr in &self.attributes { - if attr.name == name { - return Some(&attr.value); - } - } - None - } - - /// Returns whether the element has the given name and namespace. - /// - /// # Examples - /// - /// ``` - /// use minidom::Element; - /// - /// let elem = Element::builder("name").ns("namespace").build(); - /// - /// assert_eq!(elem.is("name", "namespace"), true); - /// assert_eq!(elem.is("name", "wrong"), false); - /// assert_eq!(elem.is("wrong", "namespace"), false); - /// 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()) - } - - pub fn from_reader(reader: &mut EventReader) -> Result { - loop { - let e = reader.next()?; - match e { - ReaderEvent::StartElement { name, attributes, namespace } => { - let attributes = attributes.into_iter() - .map(|o| Attribute::new(o.name.local_name, o.value)) - .collect(); - let ns = if let Some(ref prefix) = name.prefix { - namespace.get(prefix) - } - else { - namespace.get(NS_NO_PREFIX) - }.map(|s| s.to_owned()); - let mut root = Element::new(name.local_name, ns, attributes); - root.from_reader_inner(reader)?; - return Ok(root); - }, - ReaderEvent::EndDocument => { - return Err(Error::EndOfDocument); - }, - _ => () // TODO: may need more errors - } - } - } - - fn from_reader_inner(&mut self, reader: &mut EventReader) -> Result<(), Error> { - loop { - let e = reader.next()?; - match e { - ReaderEvent::StartElement { name, attributes, namespace } => { - let attributes = attributes.into_iter() - .map(|o| Attribute::new(o.name.local_name, o.value)) - .collect(); - let ns = if let Some(ref prefix) = name.prefix { - namespace.get(prefix) - } - else { - namespace.get(NS_NO_PREFIX) - }.map(|s| s.to_owned()); - let elem = Element::new(name.local_name, ns, attributes); - let elem_ref = self.append_child(elem); - elem_ref.from_reader_inner(reader)?; - }, - ReaderEvent::EndElement { .. } => { - // TODO: may want to check whether we're closing the correct element - return Ok(()); - }, - ReaderEvent::Characters(s) => { - self.append_text_node(s); - }, - ReaderEvent::CData(s) => { - self.append_text_node(s); - }, - ReaderEvent::EndDocument => { - return Err(Error::EndOfDocument); - }, - _ => (), // TODO: may need to implement more - } - } - } - - pub fn write_to(&self, writer: &mut EventWriter) -> Result<(), Error> { - let name = if let Some(ref ns) = self.namespace { - Name::qualified(&self.name, &ns, None) - } - else { - Name::local(&self.name) - }; - let mut start = WriterEvent::start_element(name); - if let Some(ref ns) = self.namespace { - start = start.default_ns(ns.as_ref()); - } - for attr in &self.attributes { // TODO: I think this could be done a lot more efficiently - start = start.attr(Name::local(&attr.name), &attr.value); - } - writer.write(start)?; - for child in &self.children { - match *child { - Fork::Element(ref e) => { - e.write_to(writer)?; - }, - Fork::Text(ref s) => { - writer.write(WriterEvent::characters(s))?; - }, - } - } - writer.write(WriterEvent::end_element())?; - Ok(()) - } - - /// Returns an iterator over references to the children of this element. - /// - /// # Examples - /// - /// ``` - /// use minidom::Element; - /// - /// let elem: Element = "".parse().unwrap(); - /// - /// let mut iter = elem.children(); - /// assert_eq!(iter.next().unwrap().name(), "child1"); - /// assert_eq!(iter.next().unwrap().name(), "child2"); - /// assert_eq!(iter.next().unwrap().name(), "child3"); - /// assert_eq!(iter.next(), None); - /// ``` - pub fn children<'a>(&'a self) -> Children<'a> { - Children { - iter: self.children.iter(), - } - } - - /// Returns an iterator over mutable references to the children of this element. - pub fn children_mut<'a>(&'a mut self) -> ChildrenMut<'a> { - ChildrenMut { - iter: self.children.iter_mut(), - } - } - - /// Appends a child node to the `Element`, returning the appended node. - /// - /// # Examples - /// - /// ``` - /// use minidom::Element; - /// - /// let mut elem = Element::bare("root"); - /// - /// assert_eq!(elem.children().count(), 0); - /// - /// elem.append_child(Element::bare("child")); - /// - /// { - /// let mut iter = elem.children(); - /// assert_eq!(iter.next().unwrap().name(), "child"); - /// assert_eq!(iter.next(), None); - /// } - /// - /// let child = elem.append_child(Element::bare("new")); - /// - /// assert_eq!(child.name(), "new"); - /// ``` - pub fn append_child(&mut self, mut child: Element) -> &mut Element { - if child.namespace.is_none() { - child.namespace = self.namespace.clone(); - } - self.children.push(Fork::Element(child)); - if let Fork::Element(ref mut cld) = *self.children.last_mut().unwrap() { - cld - } - else { - unreachable!() - } - } - - /// Appends a text node to an `Element`. - /// - /// # Examples - /// - /// ``` - /// use minidom::Element; - /// - /// let mut elem = Element::bare("node"); - /// - /// assert_eq!(elem.text(), ""); - /// - /// elem.append_text_node("text"); - /// - /// assert_eq!(elem.text(), "text"); - /// ``` - pub fn append_text_node>(&mut self, child: S) { - self.children.push(Fork::Text(child.into())); - } - - /// Returns the concatenation of all text nodes in the `Element`. - /// - /// # Examples - /// - /// ``` - /// use minidom::Element; - /// - /// let elem: Element = "hello, world!".parse().unwrap(); - /// - /// assert_eq!(elem.text(), "hello, world!"); - /// ``` - pub fn text(&self) -> String { - let mut ret = String::new(); - for fork in &self.children { - if let Fork::Text(ref s) = *fork { - ret += s; - } - } - ret - } - - /// Returns a reference to the first child element with the specific name and namespace, if it - /// exists in the direct descendants of this `Element`, else returns `None`. - /// - /// # Examples - /// - /// ``` - /// use minidom::Element; - /// - /// let elem: Element = r#""#.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")); - /// assert_eq!(elem.get_child("c", "ns"), None); - /// assert_eq!(elem.get_child("b", "other_ns"), None); - /// assert_eq!(elem.get_child("a", "inexistent_ns"), None); - /// ``` - pub fn get_child, NS: AsRef>(&self, name: N, namespace: NS) -> Option<&Element> { - for fork in &self.children { - if let Fork::Element(ref e) = *fork { - if e.is(name.as_ref(), namespace.as_ref()) { - return Some(e); - } - } - } - None - } - - /// Returns a mutable reference to the first child element with the specific name and namespace, - /// if it exists in the direct descendants of this `Element`, else returns `None`. - pub fn get_child_mut, NS: AsRef>(&mut self, name: N, namespace: NS) -> Option<&mut Element> { - for fork in &mut self.children { - if let Fork::Element(ref mut e) = *fork { - if e.is(name.as_ref(), namespace.as_ref()) { - return Some(e); - } - } - } - None - } - - /// Returns whether a specific child with this name and namespace exists in the direct - /// descendants of the `Element`. - /// - /// # Examples - /// - /// ``` - /// use minidom::Element; - /// - /// let elem: Element = r#""#.parse().unwrap(); - /// - /// assert_eq!(elem.has_child("a", "other_ns"), true); - /// assert_eq!(elem.has_child("a", "ns"), true); - /// assert_eq!(elem.has_child("a", "inexistent_ns"), false); - /// assert_eq!(elem.has_child("b", "ns"), true); - /// assert_eq!(elem.has_child("b", "other_ns"), false); - /// assert_eq!(elem.has_child("b", "inexistent_ns"), false); - /// ``` - pub fn has_child, NS: AsRef>(&self, name: N, namespace: NS) -> bool { - self.get_child(name, namespace).is_some() - } -} - -/// An iterator over references to children of an `Element`. -pub struct Children<'a> { - iter: slice::Iter<'a, Fork>, -} - -impl<'a> Iterator for Children<'a> { - type Item = &'a Element; - - fn next(&mut self) -> Option<&'a Element> { - while let Some(item) = self.iter.next() { - if let Fork::Element(ref child) = *item { - return Some(child); - } - } - None - } -} - -/// An iterator over mutable references to children of an `Element`. -pub struct ChildrenMut<'a> { - iter: slice::IterMut<'a, Fork>, -} - -impl<'a> Iterator for ChildrenMut<'a> { - type Item = &'a mut Element; - - fn next(&mut self) -> Option<&'a mut Element> { - while let Some(item) = self.iter.next() { - if let Fork::Element(ref mut child) = *item { - return Some(child); - } - } - None - } -} - -/// A builder for `Element`s. -pub struct ElementBuilder { - name: String, - text: Option, - namespace: Option, - attributes: Vec, -} - -impl ElementBuilder { - /// Sets the namespace. - pub fn ns>(mut self, namespace: S) -> ElementBuilder { - self.namespace = Some(namespace.into()); - self - } - - /// Sets an attribute. - pub fn attr, V: Into>(mut self, name: S, value: V) -> ElementBuilder { - self.attributes.push(Attribute::new(name, value)); - self - } - - /// Sets the inner text. - pub fn text>(mut self, text: S) -> ElementBuilder { - self.text = Some(text.into()); - self - } - - /// Builds the `Element`. - pub fn build(self) -> Element { - let mut elem = Element::new(self.name, self.namespace, self.attributes); - if let Some(text) = self.text { - elem.append_text_node(text); - } - elem - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use xml::reader::EventReader; - use xml::writer::EventWriter; - - const TEST_STRING: &'static str = r#"meownya"#; - - fn build_test_tree() -> Element { - let mut root = Element::builder("root") - .ns("root_ns") - .attr("a", "b") - .build(); - root.append_text_node("meow"); - let child = Element::builder("child") - .attr("c", "d") - .build(); - root.append_child(child); - let other_child = Element::builder("child") - .ns("child_ns") - .attr("d", "e") - .build(); - root.append_child(other_child); - root.append_text_node("nya"); - root - } - - #[test] - fn reader_works() { - use std::io::Cursor; - let mut reader = EventReader::new(Cursor::new(TEST_STRING)); - assert_eq!(Element::from_reader(&mut reader).unwrap(), build_test_tree()); - } - - #[test] - fn writer_works() { - let root = build_test_tree(); - let mut out = Vec::new(); - { - let mut writer = EventWriter::new(&mut out); - root.write_to(&mut writer).unwrap(); - } - assert_eq!(String::from_utf8(out).unwrap(), TEST_STRING); - } - - #[test] - fn builder_works() { - let elem = Element::builder("a") - .ns("b") - .attr("c", "d") - .text("e") - .build(); - assert_eq!(elem.name(), "a"); - assert_eq!(elem.ns(), Some("b")); - assert_eq!(elem.attr("c"), Some("d")); - assert_eq!(elem.attr("x"), None); - assert_eq!(elem.text(), "e"); - assert!(elem.is("a", "b")); - } - - #[test] - fn children_iter_works() { - let root = build_test_tree(); - let mut iter = root.children(); - assert!(iter.next().unwrap().is("child", "root_ns")); - assert!(iter.next().unwrap().is("child", "child_ns")); - assert_eq!(iter.next(), None); - } - - #[test] - fn get_child_works() { - let root = build_test_tree(); - assert_eq!(root.get_child("child", "inexistent_ns"), None); - assert_eq!(root.get_child("not_a_child", "root_ns"), None); - assert!(root.get_child("child", "root_ns").unwrap().is("child", "root_ns")); - assert!(root.get_child("child", "child_ns").unwrap().is("child", "child_ns")); - assert_eq!(root.get_child("child", "root_ns").unwrap().attr("c"), Some("d")); - assert_eq!(root.get_child("child", "child_ns").unwrap().attr("d"), Some("e")); - } - - #[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"); - 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("grandchild", "root_ns").unwrap().ns(), root.ns()); - } -} +pub use element::{Element, Node, Children, ChildrenMut, ElementBuilder}; +pub use convert::{IntoElements, IntoAttributeValue}; diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..b9263446cd110826889fbd63e7d9d560d4188e6a --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,96 @@ +use std::io::Cursor; + +use std::iter::Iterator; + +use xml::reader::EventReader; +use xml::writer::EventWriter; + +use element::Element; + +const TEST_STRING: &'static str = r#"meownya"#; + +fn build_test_tree() -> Element { + let mut root = Element::builder("root") + .ns("root_ns") + .attr("a", "b") + .build(); + root.append_text_node("meow"); + let child = Element::builder("child") + .attr("c", "d") + .build(); + root.append_child(child); + let other_child = Element::builder("child") + .ns("child_ns") + .attr("d", "e") + .build(); + root.append_child(other_child); + root.append_text_node("nya"); + root +} + +#[test] +fn reader_works() { + let mut reader = EventReader::new(Cursor::new(TEST_STRING)); + assert_eq!(Element::from_reader(&mut reader).unwrap(), build_test_tree()); +} + +#[test] +fn writer_works() { + let root = build_test_tree(); + let mut out = Vec::new(); + { + let mut writer = EventWriter::new(&mut out); + root.write_to(&mut writer).unwrap(); + } + assert_eq!(String::from_utf8(out).unwrap(), TEST_STRING); +} + +#[test] +fn builder_works() { + let elem = Element::builder("a") + .ns("b") + .attr("c", "d") + .append(Element::builder("child")) + .append("e") + .build(); + assert_eq!(elem.name(), "a"); + assert_eq!(elem.ns(), Some("b")); + assert_eq!(elem.attr("c"), Some("d")); + assert_eq!(elem.attr("x"), None); + assert_eq!(elem.text(), "e"); + assert!(elem.has_child("child", "b")); + assert!(elem.is("a", "b")); +} + +#[test] +fn children_iter_works() { + let root = build_test_tree(); + let mut iter = root.children(); + assert!(iter.next().unwrap().is("child", "root_ns")); + assert!(iter.next().unwrap().is("child", "child_ns")); + assert_eq!(iter.next(), None); +} + +#[test] +fn get_child_works() { + let root = build_test_tree(); + assert_eq!(root.get_child("child", "inexistent_ns"), None); + assert_eq!(root.get_child("not_a_child", "root_ns"), None); + assert!(root.get_child("child", "root_ns").unwrap().is("child", "root_ns")); + assert!(root.get_child("child", "child_ns").unwrap().is("child", "child_ns")); + assert_eq!(root.get_child("child", "root_ns").unwrap().attr("c"), Some("d")); + assert_eq!(root.get_child("child", "child_ns").unwrap().attr("d"), Some("e")); +} + +#[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"); + 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() + .ns(), root.ns()); +}