Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions src/pretty_print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,3 +513,200 @@ fn need_quotes(string: &str) -> bool {
|| string.parse::<i64>().is_ok()
|| string.parse::<f64>().is_ok()
}

#[cfg(test)]
mod tests {
use super::*;
use sxd_document::dom::{ChildOfElement, ChildOfRoot};
use sxd_document::parser;

/// helper function
fn first_element(package: &sxd_document::Package) -> Element<'_> {
let doc = package.as_document();
for child in doc.root().children() {
if let ChildOfRoot::Element(e) = child {
return e;
}
}
panic!("No root element found");
}

#[test]
/// Escapes XML entities and invisible characters for safe display.
/// Tests the method on a few hardcoded characters.
fn handle_special_chars_escapes() {
let input = "& < > \" ' \u{2061} \u{2062} \u{2063} \u{2064} x";
let expected = "&amp; &lt; &gt; &quot; &apos; &#x2061; &#x2062; &#x2063; &#x2064; x";
assert_eq!(handle_special_chars(input), expected);
}

#[test]
/// Formats a leaf element as a single line with escaped text.
fn format_element_leaf_text() {
let package = parser::parse("<math><mi>&amp;</mi></math>").unwrap();
let math = first_element(&package);
let mi = math
.children()
.iter()
.find_map(|c| match c {
ChildOfElement::Element(e) => Some(*e),
_ => None,
})
.unwrap();
assert_eq!(format_element(mi, 0), " <mi>&amp;</mi>\n");
}

#[test]
/// Formats a nested element with indentation and newlines.
fn format_element_nested() {
let package = parser::parse("<math><mi>x</mi><mo>+</mo></math>").unwrap();
let math = first_element(&package);
let rendered = format_element(math, 0);
assert!(rendered.starts_with(" <math>\n"));
assert!(rendered.contains("\n <mi>x</mi>\n"));
assert!(rendered.contains("\n <mo>+</mo>\n"));
assert!(rendered.ends_with("</math>\n"));
}

#[test]
/// Escapes special characters in attribute values.
fn format_attrs_escapes() {
let package = parser::parse("<math a=\"&amp;\" b=\"&lt;\"></math>").unwrap();
let math = first_element(&package);
let rendered = format_attrs(&math.attributes());
assert!(rendered.contains(" a='&amp;'"));
assert!(rendered.contains(" b='&lt;'"));
}

#[test]
/// Preserves non-BMP characters from a literal XML form.
fn format_element_non_bmp_character_literal() {
let package = parser::parse("<math><mi>𝞪</mi></math>").unwrap();
let math = first_element(&package);
let mi = math
.children()
.iter()
.find_map(|c| match c {
ChildOfElement::Element(e) => Some(*e),
_ => None,
})
.unwrap();
let rendered = format_element(mi, 0);
assert!(rendered.contains("𝞪"));
}

#[test]
/// Preserves non-BMP characters from a numeric XML form.
fn format_element_non_bmp_character_numeric() {
let package = parser::parse("<math><mi>&#x1d7aa;</mi></math>").unwrap();
let math = first_element(&package);
let mi = math
.children()
.iter()
.find_map(|c| match c {
ChildOfElement::Element(e) => Some(*e),
_ => None,
})
.unwrap();
let rendered = format_element(mi, 0);
assert!(rendered.contains("𝞪"));
}

#[test]
/// Evaluates non-BMP literal text through sxd_xpath.
fn xpath_non_bmp_literal() {
use sxd_xpath::{Factory, Value};

let package = parser::parse("<math><mi>𝞪</mi></math>").unwrap();
let xpath = Factory::new().build("string(/math/mi)").unwrap().unwrap();
let context = sxd_xpath::Context::new();

let value = xpath.evaluate(&context, first_element(&package)).unwrap();
match value {
Value::String(s) => assert_eq!(s, "𝞪"),
_ => panic!("Expected string value from xpath"),
}
}

#[test]
/// Evaluates non-BMP numeric text through sxd_xpath.
fn xpath_non_bmp_numeric() {
use sxd_xpath::{Factory, Value};

let package = parser::parse("<math><mi>&#x1d7aa;</mi></math>").unwrap();
let xpath = Factory::new().build("string(/math/mi)").unwrap().unwrap();
let context = sxd_xpath::Context::new();

let value = xpath.evaluate(&context, first_element(&package)).unwrap();
match value {
Value::String(s) => assert_eq!(s, "𝞪"),
_ => panic!("Expected string value from xpath"),
}
}

#[test]
/// Evaluates non-BMP literal text with a MathML namespace-qualified XPath.
fn xpath_non_bmp_namespace_literal() {
use sxd_xpath::{Factory, Value};

let xml = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>𝞪</mi></math>";
let package = parser::parse(xml).unwrap();
let xpath = Factory::new()
.build("string(/m:math/m:mi)")
.unwrap()
.unwrap();
let mut context = sxd_xpath::Context::new();
context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");

let value = xpath.evaluate(&context, first_element(&package)).unwrap();
match value {
Value::String(s) => assert_eq!(s, "𝞪"),
_ => panic!("Expected string value from xpath"),
}
}

#[test]
/// Evaluates non-BMP numeric text with a MathML namespace-qualified XPath.
fn xpath_non_bmp_namespace_numeric() {
use sxd_xpath::{Factory, Value};

let xml = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>&#120746;</mi></math>";
let package = parser::parse(xml).unwrap();
let xpath = Factory::new()
.build("string(/m:math/m:mi)")
.unwrap()
.unwrap();
let mut context = sxd_xpath::Context::new();
context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");

let value = xpath.evaluate(&context, first_element(&package)).unwrap();
match value {
Value::String(s) => assert_eq!(s, "𝞪"),
_ => panic!("Expected string value from xpath"),
}
}

#[test]
/// Extracts a text node via XPath (nodeset result) and verifies the non-BMP character survives.
fn xpath_non_bmp_text_nodeset() {
use sxd_xpath::{Factory, Value};

let xml = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>𝞪</mi></math>";
let package = parser::parse(xml).unwrap();
let xpath = Factory::new().build("/m:math/m:mi/text()").unwrap().unwrap();
let mut context = sxd_xpath::Context::new();
context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");

let value = xpath.evaluate(&context, first_element(&package)).unwrap();
match value {
Value::Nodeset(nodes) => {
let ordered = nodes.document_order();
let node = ordered.first().expect("Expected one text node");
let text = node.text().expect("Expected text node");
assert_eq!(text.text(), "𝞪");
assert_eq!(ordered.len(), 1);
}
_ => panic!("Expected nodeset value from xpath"),
}
}
}
Loading