use sup_xml::{encoding, parse_bytes, ErrorDomain, NodeKind, ParseOptions};
const ISO_8859_1_MINIMAL: &[u8] =
b"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><r>caf\xe9</r>";
#[test]
fn iso_8859_1_minimal_rejected_when_auto_transcode_disabled() {
let opts = ParseOptions { auto_transcode: false, ..Default::default() };
let err = parse_bytes(ISO_8859_1_MINIMAL, &opts)
.expect_err("strict UTF-8 mode rejects Latin-1");
assert_eq!(
err.domain,
ErrorDomain::Encoding,
"expected an Encoding error, got: {err:?}",
);
assert!(
err.message.contains("invalid UTF-8"),
"expected message to mention invalid UTF-8, got: {:?}",
err.message,
);
}
const TRANSITIONS_TUTORIAL: &[u8] =
include_bytes!("../../../tests/assets/xml/transitions_tutorial.xml");
#[test]
fn transitions_tutorial_rejected_when_auto_transcode_disabled() {
let opts = ParseOptions { auto_transcode: false, ..Default::default() };
let err = parse_bytes(TRANSITIONS_TUTORIAL, &opts)
.expect_err("strict UTF-8 mode rejects the ISO-8859-1 fixture");
assert_eq!(
err.domain,
ErrorDomain::Encoding,
"expected an Encoding error, got: {err:?}",
);
assert!(
err.message.contains("invalid UTF-8"),
"expected message to mention invalid UTF-8, got: {:?}",
err.message,
);
}
#[test]
fn iso_8859_1_minimal_parses_via_manual_transcoding() {
let utf8 = encoding::transcode_to_utf8(ISO_8859_1_MINIMAL)
.expect("Latin-1 transcodes cleanly");
let opts = ParseOptions { auto_transcode: false, ..Default::default() };
let doc = parse_bytes(&utf8, &opts).expect("transcoded UTF-8 parses");
let root = doc.root();
assert_eq!(root.kind, NodeKind::Element);
assert_eq!(root.name(), "r");
let text = root.children().find_map(|n| n.text_content()).unwrap_or("");
assert_eq!(text, "café", "got: {text:?}");
}
#[test]
fn transitions_tutorial_xml_parses_via_default_auto_transcode() {
let doc = parse_bytes(TRANSITIONS_TUTORIAL, &ParseOptions::default())
.expect("default parses Latin-1 fixture");
assert_eq!(doc.root().kind, NodeKind::Element, "expected a root element");
}
#[test]
fn windows_1252_ellipsis_parses_via_default_auto_transcode() {
let bytes: &[u8] =
b"<?xml version=\"1.0\" encoding=\"Windows-1252\"?><r>foo\x85bar</r>";
let doc = parse_bytes(bytes, &ParseOptions::default()).expect("default parses Windows-1252");
let root = doc.root();
assert_eq!(root.kind, NodeKind::Element);
let text = root.children().find_map(|n| n.text_content()).unwrap_or("");
assert_eq!(text, "foo…bar", "got: {text:?}");
}
#[test]
fn truly_unknown_encoding_label_returns_encoding_error() {
let bytes: &[u8] =
b"<?xml version=\"1.0\" encoding=\"definitely-not-a-real-encoding\"?><r/>";
let err = encoding::transcode_to_utf8(bytes)
.expect_err("nonsense encoding label must error");
assert_eq!(err.domain, ErrorDomain::Encoding);
}
#[test]
fn auto_transcode_parses_iso_8859_1_directly() {
let opts = ParseOptions { auto_transcode: true, ..Default::default() };
let doc = parse_bytes(ISO_8859_1_MINIMAL, &opts)
.expect("auto_transcode handles Latin-1");
let root = doc.root();
assert_eq!(root.kind, NodeKind::Element);
let text = root.children().find_map(|n| n.text_content()).unwrap_or("");
assert_eq!(text, "café", "got: {text:?}");
}
#[test]
fn auto_transcode_parses_utf16_be_directly() {
let bytes: &[u8] = &[
0xFE, 0xFF,
0x00, 0x3C, 0x00, 0x72, 0x00, 0x2F, 0x00, 0x3E,
];
let opts = ParseOptions { auto_transcode: true, ..Default::default() };
let doc = parse_bytes(bytes, &opts)
.expect("auto_transcode handles UTF-16BE");
let root = doc.root();
assert_eq!(root.kind, NodeKind::Element);
assert_eq!(root.name(), "r");
}
#[test]
fn auto_transcode_parses_gb2312_directly_via_encoding_rs() {
let bytes: &[u8] =
b"<?xml version=\"1.0\" encoding=\"GB2312\"?><r>\xD6\xD0</r>";
let opts = ParseOptions { auto_transcode: true, ..Default::default() };
let doc = parse_bytes(bytes, &opts)
.expect("auto_transcode + encoding_rs handles GB2312");
let root = doc.root();
assert_eq!(root.kind, NodeKind::Element);
let text = root.children().find_map(|n| n.text_content()).unwrap_or("");
assert_eq!(text, "中", "got: {text:?}");
}
#[test]
fn default_options_auto_transcode_is_on() {
assert!(ParseOptions::default().auto_transcode,
"auto_transcode must be on by default for libxml2 parity");
}
#[test]
fn parse_bytes_default_path_handles_iso_8859_1() {
let doc = parse_bytes(ISO_8859_1_MINIMAL, &ParseOptions::default())
.expect("default parses Latin-1");
let root = doc.root();
assert_eq!(root.kind, NodeKind::Element);
let text = root.children().find_map(|n| n.text_content()).unwrap_or("");
assert_eq!(text, "café", "got: {text:?}");
}
#[test]
fn auto_transcode_is_zero_cost_for_utf8_input() {
let utf8: &[u8] = b"<r><a>1</a><b>2</b></r>";
let strict = parse_bytes(utf8, &ParseOptions::default()).unwrap();
let auto = parse_bytes(utf8, &ParseOptions { auto_transcode: true, ..Default::default() }).unwrap();
for doc in [&strict, &auto] {
let root = doc.root();
assert_eq!(root.kind, NodeKind::Element);
assert_eq!(root.name(), "r");
let kids: Vec<&str> = root.children()
.filter(|n| n.kind == NodeKind::Element)
.map(|n| n.name())
.collect();
assert_eq!(kids.len(), 2);
assert_eq!(kids[0], "a");
assert_eq!(kids[1], "b");
}
}