#![crate_name = "xml"]
#![crate_type = "lib" ]
#![forbid(non_camel_case_types)]
#![warn(missing_docs)]
#![cfg_attr(feature = "bench", feature(test))]
pub use parser::Event;
pub use parser::Parser;
pub use parser::ParserError;
pub use element::ChildElements;
pub use element::Element;
pub use element_builder::ElementBuilder;
pub use element_builder::BuilderError;
use std::char;
use std::fmt;
use std::collections::HashMap;
mod parser;
mod element;
mod element_builder;
#[inline]
pub fn escape(input: &str) -> String {
let mut result = String::with_capacity(input.len());
for c in input.chars() {
match c {
'&' => result.push_str("&"),
'<' => result.push_str("<"),
'>' => result.push_str(">"),
'\'' => result.push_str("'"),
'"' => result.push_str("""),
o => result.push(o)
}
}
result
}
#[inline]
pub fn unescape(input: &str) -> Result<String, String> {
let mut result = String::with_capacity(input.len());
let mut it = input.split('&');
if let Some(sub) = it.next() {
result.push_str(sub);
}
for sub in it {
match sub.find(';') {
Some(idx) => {
let ent = &sub[..idx];
match ent {
"quot" => result.push('"'),
"apos" => result.push('\''),
"gt" => result.push('>'),
"lt" => result.push('<'),
"amp" => result.push('&'),
ent => {
let val = if ent.starts_with("#x") {
u32::from_str_radix(&ent[2..], 16).ok()
} else if ent.starts_with("#") {
u32::from_str_radix(&ent[1..], 10).ok()
} else {
None
};
match val.and_then(char::from_u32) {
Some(c) => result.push(c),
None => return Err(format!("&{};", ent))
}
}
}
result.push_str(&sub[idx+1..]);
}
None => return Err("&".to_string() + sub)
}
}
Ok(result)
}
#[derive(Clone, PartialEq, Debug)]
pub enum Xml {
ElementNode(Element),
CharacterNode(String),
CDATANode(String),
CommentNode(String),
PINode(String)
}
#[derive(PartialEq, Eq, Debug)]
pub struct StartTag {
pub name: String,
pub ns: Option<String>,
pub prefix: Option<String>,
pub attributes: HashMap<(String, Option<String>), String>
}
#[derive(PartialEq, Eq, Debug)]
pub struct EndTag {
pub name: String,
pub ns: Option<String>,
pub prefix: Option<String>
}
impl fmt::Display for Xml {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Xml::ElementNode(ref elem) => elem.fmt(f),
Xml::CharacterNode(ref data) => write!(f, "{}", escape(&data)),
Xml::CDATANode(ref data) => write!(f, "<![CDATA[{}]]>", &data),
Xml::CommentNode(ref data) => write!(f, "<!--{}-->", &data),
Xml::PINode(ref data) => write!(f, "<?{}?>", &data)
}
}
}
#[cfg(test)]
mod lib_tests {
use super::{Xml, Element, escape, unescape};
#[test]
fn test_escape() {
let esc = escape("&<>'\"");
assert_eq!(esc, "&<>'"");
}
#[test]
fn test_unescape() {
let unesc = unescape("&lt;<>'"“”&"");
assert_eq!(unesc.as_ref().map(|x| &x[..]), Ok("<<>'\"\u{201c}\u{201d}&\""));
}
#[test]
fn test_unescape_invalid() {
let unesc = unescape("& ");
assert_eq!(unesc.as_ref().map_err(|x| &x[..]), Err(" "));
}
#[test]
fn test_show_element() {
let elem = Element::new("a".to_string(), None, vec![]);
assert_eq!(format!("{}", elem), "<a/>");
let elem = Element::new("a".to_string(), None,
vec![("href".to_string(), None,
"http://rust-lang.org".to_string())]);
assert_eq!(format!("{}", elem), "<a href='http://rust-lang.org'/>");
let mut elem = Element::new("a".to_string(), None, vec![]);
elem.tag(Element::new("b".to_string(), None, vec![]));
assert_eq!(format!("{}", elem), "<a><b/></a>");
let mut elem = Element::new("a".to_string(), None,
vec![("href".to_string(), None,
"http://rust-lang.org".to_string())]);
elem.tag(Element::new("b".to_string(), None, vec![]));
assert_eq!(format!("{}", elem), "<a href='http://rust-lang.org'><b/></a>");
}
#[test]
fn test_show_element_xmlns() {
let elem: Element = "<a xmlns='urn:test'/>".parse().unwrap();
assert_eq!(format!("{}", elem), "<a xmlns='urn:test'/>");
let elem: Element = "<a xmlns='urn:test'><b xmlns='urn:toast'/></a>".parse().unwrap();
assert_eq!(format!("{}", elem), "<a xmlns='urn:test'><b xmlns='urn:toast'/></a>");
let elem = Element::new("a".to_string(), Some("urn:test".to_string()),
vec![("href".to_string(), None,
"http://rust-lang.org".to_string())]);
assert_eq!(format!("{}", elem), "<a xmlns='urn:test' href='http://rust-lang.org'/>");
}
#[test]
fn test_show_characters() {
let chars = Xml::CharacterNode("some text".to_string());
assert_eq!(format!("{}", chars), "some text");
}
#[test]
fn test_show_cdata() {
let chars = Xml::CDATANode("some text".to_string());
assert_eq!(format!("{}", chars), "<![CDATA[some text]]>");
}
#[test]
fn test_show_comment() {
let chars = Xml::CommentNode("some text".to_string());
assert_eq!(format!("{}", chars), "<!--some text-->");
}
#[test]
fn test_show_pi() {
let chars = Xml::PINode("xml version='1.0'".to_string());
assert_eq!(format!("{}", chars), "<?xml version='1.0'?>");
}
#[test]
fn test_content_str() {
let mut elem = Element::new("a".to_string(), None, vec![]);
elem.pi("processing information".to_string())
.cdata("<hello/>".to_string())
.tag_stay(Element::new("b".to_string(), None, vec![]))
.text("World".to_string())
.comment("Nothing to see".to_string());
assert_eq!(elem.content_str(), "<hello/>World");
}
}
#[cfg(test)]
#[cfg(feature = "bench")]
mod lib_bench {
extern crate test;
use std::iter::repeat;
use self::test::Bencher;
use super::{escape, unescape};
#[bench]
fn bench_escape(bh: &mut Bencher) {
let input: String = repeat("&<>'\"").take(100).collect();
bh.iter(|| {
escape(&input)
});
bh.bytes = input.len() as u64;
}
#[bench]
fn bench_unescape(bh: &mut Bencher) {
let input: String = repeat("&<>'"").take(50).collect();
bh.iter(|| {
unescape(&input)
});
bh.bytes = input.len() as u64;
}
}