extern crate quick_xml;
use std::io::BufRead;
use std::str::from_utf8;
use quick_xml::errors::Result;
use quick_xml::reader::Reader;
use quick_xml::events::{Event, BytesStart};
use std::fmt;
#[test]
fn sample_1_short() {
test(include_bytes!("documents/sample_1.xml"),
include_bytes!("documents/sample_1_short.txt"),
true);
}
#[test]
fn sample_1_full() {
test(include_bytes!("documents/sample_1.xml"),
include_bytes!("documents/sample_1_full.txt"),
false);
}
#[test]
fn sample_2_short() {
test(include_bytes!("documents/sample_2.xml"),
include_bytes!("documents/sample_2_short.txt"),
true);
}
#[test]
fn sample_2_full() {
test(include_bytes!("documents/sample_2.xml"),
include_bytes!("documents/sample_2_full.txt"),
false);
}
#[test]
fn sample_ns_short() {
test(include_bytes!("documents/sample_ns.xml"),
include_bytes!("documents/sample_ns_short.txt"),
true);
}
#[test]
fn eof_1() {
test(br#"<?xml"#, br#"Error: XmlDecl"#, true);
}
#[test]
fn bad_1() {
test(br#"<?xml&.,"#, br#"1:6 Error: XmlDecl"#, true);
}
#[test]
fn dashes_in_comments() {
test(br#"<!-- comment -- --><hello/>"#,
br#"
|Error: Unexpected token '--'
"#,
true);
test(br#"<!-- comment ---><hello/>"#,
br#"
|Error: Unexpected token '--'
"#,
true);
}
#[test]
fn tabs_1() {
test(b"\t<a>\t<b/></a>",
br#"
StartElement(a)
EmptyElement(b)
EndElement(a)
EndDocument
"#,
true);
}
#[test]
fn issue_83_duplicate_attributes() {
test(br#"<hello><some-tag a='10' a="20"/></hello>"#,
b"
|StartElement(hello)
|1:30 EmptyElement(some-tag, attr-error: error while parsing \
attribute at position 16: Duplicate attribute at position 9 and 16)
|EndElement(hello)
",
true);
}
#[test]
fn issue_93_large_characters_in_entity_references() {
test(r#"<hello>&𤶼;</hello>"#.as_bytes(),
r#"
|StartElement(hello)
|1:10 Error while escaping character at range 0..5:
|EndElement(hello)
"#
.as_bytes(),
true)
}
#[test]
fn issue_98_cdata_ending_with_right_bracket() {
test(br#"<hello><![CDATA[Foo [Bar]]]></hello>"#,
br#"
|StartElement(hello)
|Characters()
|CData("Foo [Bar]")
|Characters()
|EndElement(hello)
|EndDocument
"#,
false)
}
#[test]
fn issue_105_unexpected_double_dash() {
test(br#"<hello>-- </hello>"#,
br#"
|StartElement(hello)
|Characters("-- ")
|EndElement(hello)
|EndDocument
"#,
false);
test(br#"<hello>--</hello>"#,
br#"
|StartElement(hello)
|Characters("--")
|EndElement(hello)
|EndDocument
"#,
false);
test(br#"<hello>--></hello>"#,
br#"
|StartElement(hello)
|Characters("-->")
|EndElement(hello)
|EndDocument
"#,
false);
test(br#"<hello><![CDATA[--]]></hello>"#,
br#"
|StartElement(hello)
|Characters()
|CData("--")
|Characters()
|EndElement(hello)
|EndDocument
"#,
false);
}
#[test]
fn issue_attributes_have_no_default_namespace() {
test(br#"<hello xmlns="urn:foo" x="y"/>"#,
br#"
|EmptyElement({urn:foo}hello [x="y"])
|EndDocument
"#,
true);
}
#[test]
fn issue_default_namespace_on_outermost_element() {
test(br#"<hello xmlns="urn:foo"/>"#,
br#"
|EmptyElement({urn:foo}hello)
|EndDocument
"#,
true);
}
#[test]
fn default_namespace_applies_to_end_elem() {
test(br#"<hello xmlns="urn:foo" x="y">
<inner/>
</hello>"#,
br#"
|StartElement({urn:foo}hello [x="y"])
|EmptyElement({urn:foo}inner)
|EndElement({urn:foo}hello)
|EndDocument
"#,
true);
}
fn convert_to_quick_xml(s: &str) -> String {
let mut s = match s.trim() {
ts if ts.starts_with('|') => &ts[1..],
s => s,
};
if !s.is_empty() && s.as_bytes()[0] >= b'0' && s.as_bytes()[0] <= b'9' {
let p = s.chars().position(|c| c == ' ').unwrap();
s = &s[(p + 1)..];
}
if s.starts_with("Whitespace") {
format!("Characters{}", &s[10..])
} else {
format!("{}", s)
}
}
fn test(input: &[u8], output: &[u8], is_short: bool) {
let mut reader = Reader::from_reader(input);
reader
.trim_text(is_short)
.check_comments(true)
.expand_empty_elements(false);
let mut spec_lines = output
.lines()
.map(|line| convert_to_quick_xml(&line.unwrap()))
.filter(|line| !line.trim().is_empty())
.enumerate();
let mut buf = Vec::new();
if !is_short {
reader.read_event(&mut buf).unwrap();
}
loop {
{
let line = {
let e = reader.read_namespaced_event(&mut buf);
format!("{}", OptEvent(e))
};
if let Some((n, spec)) = spec_lines.next() {
if spec == "EndDocument" {
break;
}
if line.trim() != spec.trim() {
const SPLITTER: &'static str = "-------------------";
panic!("\n{}\nUnexpected event at line {}:\nExpected: {}\nFound: {}\n{}\n",
SPLITTER,
n + 1,
spec,
line,
SPLITTER);
}
} else {
if line == "EndDocument" {
break;
}
panic!("Unexpected event: {}", line);
}
if !is_short && line.starts_with("StartDocument") {
if let Ok(Event::Text(ref e)) = reader.read_event(&mut buf) {
if e.iter()
.any(|b| match *b {
b' ' | b'\r' | b'\n' | b'\t' => false,
_ => true,
}) {
panic!("Reader expects empty Text event after a StartDocument");
}
} else {
panic!("Reader expects empty Text event after a StartDocument");
}
}
}
buf.clear();
}
}
fn namespace_name(n: &Option<&[u8]>, name: &[u8]) -> String {
match n {
&Some(ref n) => format!("{{{}}}{}", from_utf8(n).unwrap(), from_utf8(name).unwrap()),
&None => from_utf8(name).unwrap().to_owned(),
}
}
fn make_attrs(e: &BytesStart) -> ::std::result::Result<String, String> {
let mut atts = Vec::new();
for a in e.attributes() {
match a {
Ok(a) => {
if a.key.len() < 5 || !a.key.starts_with(b"xmlns") {
atts.push(format!("{}={:?}",
from_utf8(a.key).unwrap(),
from_utf8(&*a.unescaped_value().unwrap()).unwrap()));
}
}
Err(e) => return Err(e.to_string()),
}
}
Ok(atts.join(", "))
}
struct OptEvent<'a, 'b>(Result<(Option<&'a [u8]>, Event<'b>)>);
impl<'a, 'b> fmt::Display for OptEvent<'a, 'b> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
Ok((ref n, Event::Start(ref e))) => {
let name = namespace_name(n, e.name());
match make_attrs(e) {
Ok(ref attrs) if attrs.is_empty() => write!(f, "StartElement({})", &name),
Ok(ref attrs) => write!(f, "StartElement({} [{}])", &name, &attrs),
Err(e) => write!(f, "StartElement({}, attr-error: {})", &name, &e),
}
}
Ok((ref n, Event::Empty(ref e))) => {
let name = namespace_name(n, e.name());
match make_attrs(e) {
Ok(ref attrs) if attrs.is_empty() => write!(f, "EmptyElement({})", &name),
Ok(ref attrs) => write!(f, "EmptyElement({} [{}])", &name, &attrs),
Err(e) => write!(f, "EmptyElement({}, attr-error: {})", &name, &e),
}
}
Ok((ref n, Event::End(ref e))) => {
write!(f, "EndElement({})", namespace_name(n, e.name()))
}
Ok((_, Event::Comment(ref e))) => write!(f, "Comment({:?})", from_utf8(e).unwrap()),
Ok((_, Event::CData(ref e))) => write!(f, "CData({:?})", from_utf8(e).unwrap()),
Ok((_, Event::Text(ref e))) => {
match e.unescaped() {
Ok(c) => {
if c.is_empty() {
write!(f, "Characters()")
} else {
write!(f, "Characters({:?})", from_utf8(&*c).unwrap())
}
}
Err(ref e) => write!(f, "{}", e),
}
}
Ok((_, Event::Decl(ref e))) => {
let version = from_utf8(e.version().unwrap()).unwrap();
let encoding = from_utf8(e.encoding().unwrap().unwrap()).unwrap();
write!(f, "StartDocument({}, {})", version, encoding)
}
Ok((_, Event::Eof)) => write!(f, "EndDocument"),
Ok((_, Event::PI(ref e))) => {
write!(f, "ProcessingInstruction(PI={:?})", from_utf8(e).unwrap())
}
Err(ref e) => write!(f, "Error: {}", e.iter().last().unwrap()),
Ok((_, Event::DocType(ref e))) => write!(f, "DocType({})", from_utf8(e).unwrap()),
}
}
}