use oxml::{ErrorKind, NodeKind, parse};
#[test]
fn parses_a_minimal_document() {
let d = parse("<a/>").expect("parses");
let root = d.root_element().expect("has a root element");
assert_eq!(d.element_name(root).unwrap().local, "a");
assert!(d.children(root).is_empty());
}
#[test]
fn merges_adjacent_character_data() {
let d = parse("<a>one & <![CDATA[two]]> three</a>").expect("parses");
let root = d.root_element().unwrap();
let kids = d.children(root);
assert_eq!(kids.len(), 1, "expected a single merged text node");
assert_eq!(d.text(root), "one & two three");
}
#[test]
fn resolves_the_five_predefined_entities() {
let d = parse("<a><>&'"</a>").expect("parses");
assert_eq!(d.text(d.root_element().unwrap()), "<>&'\"");
}
#[test]
fn resolves_numeric_character_references() {
let d = parse("<a>AB</a>").expect("parses");
assert_eq!(d.text(d.root_element().unwrap()), "AB");
}
#[test]
fn refuses_to_expand_custom_entities() {
let src = r#"<!DOCTYPE a [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<a>&xxe;</a>"#;
let err = parse(src).expect_err("must not expand");
assert!(matches!(err.kind, ErrorKind::UnknownEntity(_)));
}
#[test]
fn billion_laughs_does_not_expand() {
let src = r#"<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;">
]><lolz>&lol2;</lolz>"#;
let err = parse(src).expect_err("must not expand");
assert!(matches!(err.kind, ErrorKind::UnknownEntity(_)));
}
#[test]
fn namespaces_resolve_by_uri_not_prefix() {
let d = parse(r#"<r xmlns:a="urn:x" xmlns:b="urn:x"><a:e/><b:e/></r>"#)
.expect("parses");
let root = d.root_element().unwrap();
let kids = d.children(root);
let first = d.element_name(kids[0]).unwrap();
let second = d.element_name(kids[1]).unwrap();
assert_eq!(first, second);
assert_eq!(first.namespace.as_deref(), Some("urn:x"));
}
#[test]
fn default_namespace_applies_to_elements_not_attributes() {
let d = parse(r#"<r xmlns="urn:d" id="7"/>"#).expect("parses");
let root = d.root_element().unwrap();
assert_eq!(
d.element_name(root).unwrap().namespace.as_deref(),
Some("urn:d")
);
let attrs = d.attributes(root);
assert_eq!(attrs.len(), 1);
assert_eq!(attrs[0].name.namespace, None);
}
#[test]
fn undeclared_prefixes_are_rejected() {
let err = parse("<a:e/>").expect_err("prefix is not bound");
assert!(matches!(err.kind, ErrorKind::UnboundPrefix(_)));
}
#[test]
fn the_xml_prefix_is_bound_without_declaration() {
let d = parse(r#"<a xml:lang="en"/>"#).expect("parses");
let root = d.root_element().unwrap();
assert_eq!(
d.attributes(root)[0].name.namespace.as_deref(),
Some("http://www.w3.org/XML/1998/namespace")
);
}
#[test]
fn mismatched_end_tags_are_rejected() {
let err = parse("<a></b>").expect_err("mismatched");
assert!(matches!(err.kind, ErrorKind::MismatchedEndTag { .. }));
}
#[test]
fn duplicate_attributes_are_rejected() {
let err = parse(r#"<a x="1" x="2"/>"#).expect_err("duplicate");
assert!(matches!(err.kind, ErrorKind::DuplicateAttribute(_)));
}
#[test]
fn content_after_the_root_element_is_rejected() {
let err = parse("<a/><b/>").expect_err("two roots");
assert!(matches!(err.kind, ErrorKind::TrailingContent));
}
#[test]
fn a_document_needs_a_root_element() {
let err = parse("<!-- just a comment -->").expect_err("no root element");
assert!(matches!(err.kind, ErrorKind::NoRootElement));
}
#[test]
fn comments_and_processing_instructions_are_preserved() {
let d = parse("<?xml version=\"1.0\"?><a><!--c--><?pi data?></a>")
.expect("parses");
let root = d.root_element().unwrap();
let kids = d.children(root);
assert!(matches!(d.kind(kids[0]), Some(NodeKind::Comment(c)) if c == "c"));
assert!(matches!(
d.kind(kids[1]),
Some(NodeKind::ProcessingInstruction { target, data })
if target == "pi" && data == "data"
));
}
#[test]
fn a_doctype_with_a_bracketed_subset_does_not_end_early() {
let d =
parse("<!DOCTYPE a [<!ELEMENT a (#PCDATA)>]><a>x</a>").expect("parses");
assert_eq!(d.text(d.root_element().unwrap()), "x");
}
#[test]
fn errors_report_a_usable_line_and_column() {
let src = "<a>\n <b>\n</a>";
let err = parse(src).expect_err("mismatched");
let (line, col) = err.line_column(src);
assert!(line >= 2, "line was {line}");
assert!(col >= 1);
}
#[test]
fn unterminated_constructs_are_rejected_not_hung() {
for src in [
"<a>",
"<a><!-- unterminated",
"<a attr='unterminated>",
"<?pi unterminated",
] {
assert!(parse(src).is_err(), "should reject: {src}");
}
}