#![crate_name = "xml"]
#![crate_type = "lib"]
#![forbid(non_camel_case_types)]
#![warn(missing_docs)]
#![cfg_attr(feature = "bench", feature(test))]
pub use crate::element::ChildElements;
pub use crate::element::Element;
pub use crate::element_builder::BuilderError;
pub use crate::element_builder::ElementBuilder;
pub use crate::parser::Event;
pub use crate::parser::Parser;
pub use crate::parser::ParserError;
use std::char;
use std::collections::HashMap;
use std::fmt;
mod element;
mod element_builder;
mod parser;
#[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_owned() + 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::{escape, unescape, Element, Xml};
#[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_owned(), None, vec![]);
assert_eq!(format!("{}", elem), "<a/>");
let elem = Element::new(
"a".to_owned(),
None,
vec![("href".to_owned(), None, "http://rust-lang.org".to_owned())],
);
assert_eq!(format!("{}", elem), "<a href='http://rust-lang.org'/>");
let mut elem = Element::new("a".to_owned(), None, vec![]);
elem.tag(Element::new("b".to_owned(), None, vec![]));
assert_eq!(format!("{}", elem), "<a><b/></a>");
let mut elem = Element::new(
"a".to_owned(),
None,
vec![("href".to_owned(), None, "http://rust-lang.org".to_owned())],
);
elem.tag(Element::new("b".to_owned(), 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_owned(),
Some("urn:test".to_owned()),
vec![("href".to_owned(), None, "http://rust-lang.org".to_owned())],
);
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_owned());
assert_eq!(format!("{}", chars), "some text");
}
#[test]
fn test_show_cdata() {
let chars = Xml::CDATANode("some text".to_owned());
assert_eq!(format!("{}", chars), "<![CDATA[some text]]>");
}
#[test]
fn test_show_comment() {
let chars = Xml::CommentNode("some text".to_owned());
assert_eq!(format!("{}", chars), "<!--some text-->");
}
#[test]
fn test_show_pi() {
let chars = Xml::PINode("xml version='1.0'".to_owned());
assert_eq!(format!("{}", chars), "<?xml version='1.0'?>");
}
#[test]
fn test_content_str() {
let mut elem = Element::new("a".to_owned(), None, vec![]);
elem.pi("processing information".to_owned())
.cdata("<hello/>".to_owned())
.tag_stay(Element::new("b".to_owned(), None, vec![]))
.text("World".to_owned())
.comment("Nothing to see".to_owned());
assert_eq!(elem.content_str(), "<hello/>World");
}
}
#[cfg(test)]
#[cfg(feature = "bench")]
mod lib_bench {
extern crate test;
use self::test::Bencher;
use super::{escape, unescape};
use std::iter::repeat;
#[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;
}
}