From 0a45a6993e8c119fffd6e6a2ce8f55b34ba070af Mon Sep 17 00:00:00 2001 From: lumi Date: Tue, 21 Feb 2017 15:46:06 +0100 Subject: [PATCH] document all the things! --- examples/articles.rs | 45 +++++++ src/attribute.rs | 19 +++ src/error.rs | 1 + src/lib.rs | 274 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 examples/articles.rs diff --git a/examples/articles.rs b/examples/articles.rs new file mode 100644 index 0000000..6db61d8 --- /dev/null +++ b/examples/articles.rs @@ -0,0 +1,45 @@ +extern crate minidom; + +use minidom::Element; + +const DATA: &'static str = r#" +
+ 10 Terrible Bugs You Would NEVER Believe Happened + + Rust fixed them all. <3 + +
+
+ BREAKING NEWS: Physical Bug Jumps Out Of Programmer's Screen + + Just kidding! + +
+
"#; + +const ARTICLE_NS: &'static str = "article"; + +#[derive(Debug)] +pub struct Article { + title: String, + body: String, +} + +fn main() { + let root: Element = DATA.parse().unwrap(); + + let mut articles: Vec
= Vec::new(); + + for child in root.children() { + if child.is("article", ARTICLE_NS) { + let title = child.get_child("title", ARTICLE_NS).unwrap().text(); + let body = child.get_child("body", ARTICLE_NS).unwrap().text(); + articles.push(Article { + title: title, + body: body.trim().to_owned(), + }); + } + } + + println!("{:?}", articles); +} diff --git a/src/attribute.rs b/src/attribute.rs index ac41f12..5d45f40 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -2,9 +2,16 @@ use xml::escape::escape_str_attribute; use std::fmt; +/// An attribute of a DOM element. +/// +/// This is of the form: `name`="`value`" +/// +/// This does not support prefixed/namespaced attributes yet. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Attribute { + /// The name of the attribute. pub name: String, + /// The value of the attribute. pub value: String, } @@ -15,6 +22,18 @@ impl fmt::Display for Attribute { } impl Attribute { + /// Construct a new attribute from the given `name` and `value`. + /// + /// # Examples + /// + /// ``` + /// use minidom::Attribute; + /// + /// let attr = Attribute::new("name", "value"); + /// + /// assert_eq!(attr.name, "name"); + /// assert_eq!(attr.value, "value"); + /// ``` pub fn new, V: Into>(name: N, value: V) -> Attribute { Attribute { name: name.into(), diff --git a/src/error.rs b/src/error.rs index db18406..38f4069 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,7 @@ use std::convert::From; use xml::writer::Error as WriterError; use xml::reader::Error as ReaderError; +/// An enum representing the possible errors. #[derive(Debug)] pub enum Error { IoError(io::Error), diff --git a/src/lib.rs b/src/lib.rs index ce8a26d..5420df8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,76 @@ +//! A minimal DOM crate built on top of xml-rs. +//! +//! This library exports an `Element` struct which represents a DOM tree. +//! +//! # Example +//! +//! Run with `cargo run --example articles`. Located in `examples/articles.rs`. +//! +//! ```rust,ignore +//! extern crate minidom; +//! +//! use minidom::Element; +//! +//! const DATA: &'static str = r#" +//!
+//! 10 Terrible Bugs You Would NEVER Believe Happened +//! +//! Rust fixed them all. <3 +//! +//!
+//!
+//! BREAKING NEWS: Physical Bug Jumps Out Of Programmer's Screen +//! +//! Just kidding! +//! +//!
+//!
"#; +//! +//! const ARTICLE_NS: &'static str = "article"; +//! +//! #[derive(Debug)] +//! pub struct Article { +//! title: String, +//! body: String, +//! } +//! +//! fn main() { +//! let root: Element = DATA.parse().unwrap(); +//! +//! let mut articles: Vec
= Vec::new(); +//! +//! for child in root.children() { +//! if child.is("article", ARTICLE_NS) { +//! let title = child.get_child("title", ARTICLE_NS).unwrap().text(); +//! let body = child.get_child("body", ARTICLE_NS).unwrap().text(); +//! articles.push(Article { +//! title: title, +//! body: body.trim().to_owned(), +//! }); +//! } +//! } +//! +//! println!("{:?}", articles); +//! } +//! ``` +//! +//! # Usage +//! +//! To use `minidom`, add this to your `Cargo.toml`: +//! +//! ```toml,ignore +//! [dependencies.minidom] +//! git = "https://gitlab.com/lumi/minidom-rs.git" +//! ``` + extern crate xml; -pub mod error; -pub mod attribute; +mod error; + +mod attribute; use std::io::prelude::*; +use std::io::Cursor; use std::convert::AsRef; @@ -13,16 +80,19 @@ use std::slice; use std::fmt; +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; -use error::Error; +pub use error::Error; -use attribute::Attribute; +pub use attribute::Attribute; #[derive(Clone, PartialEq, Eq)] +/// A struct representing a DOM Element. pub struct Element { name: String, namespace: Option, @@ -57,13 +127,40 @@ impl fmt::Debug for Element { } } +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)] -pub enum Fork { +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, @@ -73,6 +170,25 @@ impl Element { } } + /// 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(), @@ -82,15 +198,41 @@ impl Element { } } + /// 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 { @@ -100,6 +242,20 @@ impl Element { 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()) @@ -196,18 +352,57 @@ impl 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(); @@ -221,10 +416,36 @@ impl Element { } } + /// 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 { @@ -235,6 +456,23 @@ impl Element { 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 { @@ -246,6 +484,8 @@ impl Element { 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 { @@ -257,11 +497,29 @@ impl Element { 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>, } @@ -279,6 +537,7 @@ impl<'a> Iterator for Children<'a> { } } +/// An iterator over mutable references to children of an `Element`. pub struct ChildrenMut<'a> { iter: slice::IterMut<'a, Fork>, } @@ -296,6 +555,7 @@ impl<'a> Iterator for ChildrenMut<'a> { } } +/// A builder for `Element`s. pub struct ElementBuilder { name: String, text: Option, @@ -304,21 +564,25 @@ pub struct ElementBuilder { } 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 {