use pith_digest::Error;
use pith_zip::{XmlEvent, XmlReader};
fn events(input: &str) -> Vec<XmlEvent<'_>> {
XmlReader::new(input)
.collect::<Result<Vec<_>, _>>()
.expect("well-formed input must not error")
}
fn first_error(input: &str) -> Error {
let mut reader = XmlReader::new(input);
loop {
match reader.next() {
Some(Err(e)) => return e,
Some(Ok(_)) => {}
None => panic!("input {input:?} produced no error"),
}
}
}
#[test]
fn nested_elements_and_attributes() {
let doc = "<w:document xmlns:w=\"http://x\"><w:body><w:p w:id=\"42\"><w:t>hi</w:t></w:p></w:body></w:document>";
let ev = events(doc);
assert_eq!(
ev,
vec![
XmlEvent::Start {
name: "w:document",
attrs: vec![("xmlns:w", "http://x".to_string())],
},
XmlEvent::Start {
name: "w:body",
attrs: vec![],
},
XmlEvent::Start {
name: "w:p",
attrs: vec![("w:id", "42".to_string())],
},
XmlEvent::Start {
name: "w:t",
attrs: vec![],
},
XmlEvent::Text("hi".to_string()),
XmlEvent::End { name: "w:t" },
XmlEvent::End { name: "w:p" },
XmlEvent::End { name: "w:body" },
XmlEvent::End { name: "w:document" },
]
);
}
#[test]
fn entities_and_character_references_decode() {
let ev = events("<a b=\""x'AB\">1 & 2 <3> €€</a>");
assert_eq!(
ev[0],
XmlEvent::Start {
name: "a",
attrs: vec![("b", "\"x'AB".to_string())],
}
);
assert_eq!(ev[1], XmlEvent::Text("1 & 2 <3> €€".to_string()));
assert_eq!(ev[2], XmlEvent::End { name: "a" });
}
#[test]
fn comments_pi_cdata_and_self_closing() {
let doc = "<?xml version=\"1.0\"?><!-- a -- b --><?pi <not>?><r><e/><![CDATA[<raw> &]]></r>";
assert_eq!(
events(doc),
vec![
XmlEvent::Start {
name: "r",
attrs: vec![],
},
XmlEvent::Start {
name: "e",
attrs: vec![],
},
XmlEvent::End { name: "e" },
XmlEvent::Text("<raw> &".to_string()),
XmlEvent::End { name: "r" },
]
);
}
#[test]
fn doctype_with_internal_subset_is_skipped() {
let doc = "<!DOCTYPE r [ <!ELEMENT r (#PCDATA)> <!ATTLIST r a CDATA \"x>\"> ]><r>ok</r>";
assert_eq!(
events(doc),
vec![
XmlEvent::Start {
name: "r",
attrs: vec![],
},
XmlEvent::Text("ok".to_string()),
XmlEvent::End { name: "r" },
]
);
}
#[test]
fn mismatched_end_tag_is_rejected() {
assert_eq!(
first_error("<a><b></a></b>"),
Error::BadValue("xml end tag mismatch")
);
}
#[test]
fn unclosed_element_is_truncated() {
assert!(matches!(first_error("<a><b>"), Error::Truncated { .. }));
assert!(matches!(first_error("<a"), Error::Truncated { .. }));
assert!(matches!(first_error("<a></a"), Error::Truncated { .. }));
}
#[test]
fn bad_entities_are_rejected() {
assert_eq!(
first_error("<a> </a>"),
Error::BadValue("xml unknown entity")
);
assert_eq!(
first_error("<a>&</a>"),
Error::BadValue("xml unterminated entity")
);
assert_eq!(
first_error("<a>�</a>"),
Error::BadValue("xml numeric character reference")
);
}
#[test]
fn document_shape_is_enforced() {
assert_eq!(
first_error("<a/>tail"),
Error::BadValue("xml text outside root element")
);
assert_eq!(
first_error("<a/><b/>"),
Error::BadValue("xml second root element")
);
assert_eq!(
first_error("<a x=\"1\" x=\"2\"/>"),
Error::BadValue("xml duplicate attribute")
);
}
#[test]
fn errors_are_terminal() {
let mut reader = XmlReader::new("<a></b>");
assert!(matches!(reader.next(), Some(Ok(XmlEvent::Start { .. }))));
assert!(matches!(reader.next(), Some(Err(_))));
assert!(reader.next().is_none());
assert!(reader.next().is_none());
}
#[test]
fn prolog_and_epilog_whitespace_is_skipped() {
assert_eq!(
events(" <?xml version=\"1.0\"?>\n<a/>\n "),
vec![
XmlEvent::Start {
name: "a",
attrs: vec![],
},
XmlEvent::End { name: "a" },
]
);
}