use libxml::parser::Parser;
use libxml::tree::{Document, Node};
#[test]
fn xml_copy_doc_does_not_corrupt_source() {
use libxml::bindings::{xmlCopyDoc, xmlFreeDoc};
let parser = Parser::default();
let path = "tests/resources/large_doc.xml";
if std::fs::metadata(path).is_err() {
return;
}
let src = parser.parse_file(path).expect("parse");
let root = src.get_root_element().expect("root");
let sections: Vec<Node> = root
.findnodes("descendant::*[local-name()='section']")
.unwrap_or_default()
.into_iter()
.filter(|n| {
n.get_parent()
.map(|p| p.get_name() == "chapter")
.unwrap_or(false)
})
.collect();
let pre: Vec<Option<String>> = sections.iter().map(|s| s.get_attribute("xml:id")).collect();
unsafe {
let copy = xmlCopyDoc(src.doc_ptr(), 1);
assert!(!copy.is_null());
xmlFreeDoc(copy);
}
let post: Vec<Option<String>> = sections.iter().map(|s| s.get_attribute("xml:id")).collect();
for (i, (p, q)) in pre.iter().zip(post.iter()).enumerate() {
assert_eq!(p, q, "section {i} xml:id changed across xmlCopyDoc round-trip: {p:?} -> {q:?}");
}
}
#[test]
fn xml_copy_doc_does_not_corrupt_source_after_init_walks() {
use libxml::bindings::{xmlCopyDoc, xmlFreeDoc};
let path = "tests/resources/large_doc.xml";
if std::fs::metadata(path).is_err() {
return;
}
let xml = std::fs::read_to_string(path).expect("read");
let parser = Parser::default();
let src = parser.parse_string(&xml).expect("parse");
let root = src.get_root_element().unwrap();
let mut ctx = libxml::xpath::Context::new(&src).unwrap();
let _id_walk = ctx.findnodes("//*[@xml:id]", None).unwrap_or_default();
let _pi_walk = ctx
.findnodes(".//processing-instruction('latexml')", None)
.unwrap_or_default();
let sections: Vec<Node> = root
.findnodes("descendant::*[local-name()='section']")
.unwrap_or_default()
.into_iter()
.filter(|n| {
n.get_parent()
.map(|p| p.get_name() == "chapter")
.unwrap_or(false)
})
.collect();
let pre: Vec<Option<String>> = sections.iter().map(|s| s.get_attribute("xml:id")).collect();
unsafe {
let copy = xmlCopyDoc(src.doc_ptr(), 1);
assert!(!copy.is_null());
xmlFreeDoc(copy);
}
let post: Vec<Option<String>> = sections.iter().map(|s| s.get_attribute("xml:id")).collect();
for (i, (p, q)) in pre.iter().zip(post.iter()).enumerate() {
assert_eq!(p, q, "section {i} xml:id changed across copy/free: {p:?} -> {q:?}");
}
}
#[test]
fn xml_copy_doc_no_corrupt_after_unlink() {
use libxml::bindings::{xmlCopyDoc, xmlFreeDoc};
let path = "tests/resources/large_doc.xml";
if std::fs::metadata(path).is_err() {
return;
}
let xml = std::fs::read_to_string(path).expect("read");
let parser = Parser::default();
let src = parser.parse_string(&xml).expect("parse");
let root = src.get_root_element().unwrap();
let mut ctx = libxml::xpath::Context::new(&src).unwrap();
let _ = ctx.findnodes("//*[@xml:id]", None).unwrap_or_default();
let mut sections: Vec<Node> = root
.findnodes("descendant::*[local-name()='section']")
.unwrap_or_default()
.into_iter()
.filter(|n| {
n.get_parent()
.map(|p| p.get_name() == "chapter")
.unwrap_or(false)
})
.collect();
let pre: Vec<Option<String>> = sections.iter().map(|s| s.get_attribute("xml:id")).collect();
for s in sections.iter_mut() {
s.unlink_node();
}
let mid: Vec<Option<String>> = sections.iter().map(|s| s.get_attribute("xml:id")).collect();
for (i, (p, q)) in pre.iter().zip(mid.iter()).enumerate() {
assert_eq!(p, q, "section {i} xml:id changed BY unlink: {p:?} -> {q:?}");
}
unsafe {
let copy = xmlCopyDoc(src.doc_ptr(), 1);
assert!(!copy.is_null());
xmlFreeDoc(copy);
}
let post: Vec<Option<String>> = sections.iter().map(|s| s.get_attribute("xml:id")).collect();
for (i, (p, q)) in pre.iter().zip(post.iter()).enumerate() {
assert_eq!(p, q, "section {i} xml:id changed AFTER copy/free: {p:?} -> {q:?}");
}
}
#[test]
fn xml_copy_node_twice_same_source() {
use libxml::bindings::{xmlCopyNode, xmlFreeNode};
let path = "tests/resources/large_doc.xml";
if std::fs::metadata(path).is_err() {
return;
}
let xml = std::fs::read_to_string(path).expect("read");
let parser = Parser::default();
let src = parser.parse_string(&xml).expect("parse");
let root = src.get_root_element().unwrap();
let mut ctx = libxml::xpath::Context::new(&src).unwrap();
let _ = ctx.findnodes("//*[@xml:id]", None).unwrap_or_default();
let _ = ctx
.findnodes(".//processing-instruction('latexml')", None)
.unwrap_or_default();
let sections: Vec<Node> = root
.findnodes("descendant::*[local-name()='section']")
.unwrap_or_default()
.into_iter()
.filter(|n| {
n.get_parent()
.map(|p| p.get_name() == "chapter")
.unwrap_or(false)
})
.collect();
assert!(sections.len() >= 2);
let mut s1 = sections[0].clone();
s1.unlink_node();
unsafe {
let c1 = xmlCopyNode(s1.node_ptr(), 1);
assert!(!c1.is_null(), "first xmlCopyNode of S1 returned NULL");
xmlFreeNode(c1);
}
unsafe {
let c2 = xmlCopyNode(s1.node_ptr(), 1);
assert!(!c2.is_null(), "second xmlCopyNode of S1 returned NULL");
xmlFreeNode(c2);
}
}
#[test]
fn xml_copy_node_twice_different_sources_after_unlink_all() {
use libxml::bindings::{xmlCopyNode, xmlFreeNode};
let path = "tests/resources/large_doc.xml";
if std::fs::metadata(path).is_err() {
return;
}
let xml = std::fs::read_to_string(path).expect("read");
let parser = Parser::default();
let src = parser.parse_string(&xml).expect("parse");
let root = src.get_root_element().unwrap();
let mut ctx = libxml::xpath::Context::new(&src).unwrap();
let _ = ctx.findnodes("//*[@xml:id]", None).unwrap_or_default();
let _ = ctx
.findnodes(".//processing-instruction('latexml')", None)
.unwrap_or_default();
let mut sections: Vec<Node> = root
.findnodes("descendant::*[local-name()='section']")
.unwrap_or_default()
.into_iter()
.filter(|n| {
n.get_parent()
.map(|p| p.get_name() == "chapter")
.unwrap_or(false)
})
.collect();
assert!(sections.len() >= 2);
for s in sections.iter_mut() {
s.unlink_node();
}
unsafe {
let c1 = xmlCopyNode(sections[0].node_ptr(), 1);
assert!(!c1.is_null(), "first copy returned NULL");
let c2 = xmlCopyNode(sections[1].node_ptr(), 1);
assert!(
!c2.is_null(),
"SECOND copy returned NULL — this is the oxide failure mode"
);
xmlFreeNode(c1);
xmlFreeNode(c2);
}
}
#[test]
fn xml_copy_node_pair_with_split_preamble() {
use libxml::bindings::{xmlCopyNode, xmlFreeNode};
let path = "tests/resources/large_doc.xml";
if std::fs::metadata(path).is_err() {
return;
}
let xml = std::fs::read_to_string(path).expect("read");
let parser = Parser::default();
let src = parser.parse_string(&xml).expect("parse");
let mut root = src.get_root_element().unwrap();
let mut ctx = libxml::xpath::Context::new(&src).unwrap();
let _ = ctx.findnodes("//*[@xml:id]", None).unwrap_or_default();
let _ = ctx
.findnodes(".//processing-instruction('latexml')", None)
.unwrap_or_default();
if root.get_attribute("xml:id").is_none() {
root.set_attribute("xml:id", "TEMPORARY_DOCUMENT_ID").ok();
}
let _pages = root
.findnodes(
"//ltx:section | //ltx:chapter | //ltx:part | //ltx:bibliography \
| //ltx:appendix | //ltx:index",
)
.unwrap_or_default();
let mut sections: Vec<Node> = root
.findnodes("descendant::*[local-name()='section']")
.unwrap_or_default()
.into_iter()
.filter(|n| {
n.get_parent()
.map(|p| p.get_name() == "chapter")
.unwrap_or(false)
})
.collect();
for s in sections.iter_mut() {
s.unlink_node();
}
unsafe {
let c1 = xmlCopyNode(sections[0].node_ptr(), 1);
assert!(!c1.is_null(), "first copy returned NULL");
let c2 = xmlCopyNode(sections[1].node_ptr(), 1);
assert!(!c2.is_null(), "SECOND copy returned NULL — repro of oxide bug");
xmlFreeNode(c1);
xmlFreeNode(c2);
}
}
#[test]
fn xml_copy_node_pair_with_intermediate_xpath_on_source() {
use libxml::bindings::{xmlCopyNode, xmlFreeNode};
let path = "tests/resources/large_doc.xml";
if std::fs::metadata(path).is_err() {
return;
}
let xml = std::fs::read_to_string(path).expect("read");
let parser = Parser::default();
let src = parser.parse_string(&xml).expect("parse");
let mut root = src.get_root_element().unwrap();
let mut ctx = libxml::xpath::Context::new(&src).unwrap();
let _ = ctx.findnodes("//*[@xml:id]", None).unwrap_or_default();
if root.get_attribute("xml:id").is_none() {
root.set_attribute("xml:id", "TEMPORARY_DOCUMENT_ID").ok();
}
let mut sections: Vec<Node> = root
.findnodes("descendant::*[local-name()='section']")
.unwrap_or_default()
.into_iter()
.filter(|n| {
n.get_parent()
.map(|p| p.get_name() == "chapter")
.unwrap_or(false)
})
.collect();
for s in sections.iter_mut() {
s.unlink_node();
}
unsafe {
let c1 = xmlCopyNode(sections[0].node_ptr(), 1);
assert!(!c1.is_null(), "first copy returned NULL");
xmlFreeNode(c1);
}
let _r = root
.findnodes("descendant::*[local-name()='resource']")
.unwrap_or_default();
let _p = root
.findnodes(".//processing-instruction('latexml')")
.unwrap_or_default();
let _i = root
.findnodes("//*[@xml:id]")
.unwrap_or_default();
unsafe {
let c2 = xmlCopyNode(sections[1].node_ptr(), 1);
assert!(
!c2.is_null(),
"SECOND copy returned NULL after intermediate XPath — repro of oxide bug"
);
xmlFreeNode(c2);
}
}
#[test]
fn xml_copy_node_pair_full_oxide_style() {
use libxml::bindings::{xmlCopyNode, xmlNewDoc, xmlDocSetRootElement, xmlSetTreeDoc, xmlReconciliateNs};
let path = "tests/resources/large_doc.xml";
if std::fs::metadata(path).is_err() {
return;
}
let xml = std::fs::read_to_string(path).expect("read");
let parser = Parser::default();
let src = parser.parse_string(&xml).expect("parse");
let mut root = src.get_root_element().unwrap();
let mut ctx = libxml::xpath::Context::new(&src).unwrap();
let _ = ctx.findnodes("//*[@xml:id]", None).unwrap_or_default();
if root.get_attribute("xml:id").is_none() {
root.set_attribute("xml:id", "TEMPORARY_DOCUMENT_ID").ok();
}
let mut sections: Vec<Node> = root
.findnodes("descendant::*[local-name()='section']")
.unwrap_or_default()
.into_iter()
.filter(|n| {
n.get_parent()
.map(|p| p.get_name() == "chapter")
.unwrap_or(false)
})
.collect();
for s in sections.iter_mut() {
s.unlink_node();
}
let mut subdocs: Vec<Document> = Vec::new();
for (i, s) in sections.iter().enumerate() {
let sub: Document = unsafe {
let copy = xmlCopyNode(s.node_ptr(), 1);
assert!(!copy.is_null(), "dup #{i} xmlCopyNode returned NULL");
let doc_ptr = xmlNewDoc(c"1.0".as_ptr() as *const u8);
xmlDocSetRootElement(doc_ptr, copy);
xmlSetTreeDoc(copy, doc_ptr);
let _ = xmlReconciliateNs(doc_ptr, copy);
Document::new_ptr(doc_ptr)
};
let _id_walk = sub.get_root_element().unwrap()
.findnodes("//*[@xml:id]")
.unwrap_or_default();
let _src_pis = root
.findnodes(".//processing-instruction('latexml')")
.unwrap_or_default();
let _src_res = root
.findnodes("descendant::*[local-name()='resource']")
.unwrap_or_default();
subdocs.push(sub);
}
drop(sections);
drop(src);
for s in &subdocs {
let _ = s.to_string();
}
}
#[test]
fn xml_copy_node_pair_nodict_parse() {
use libxml::bindings::{
xmlCopyNode, xmlFreeDoc, xmlFreeNode, xmlReadMemory,
xmlParserOption_XML_PARSE_NODICT,
};
let path = "tests/resources/large_doc.xml";
if std::fs::metadata(path).is_err() {
return;
}
let xml = std::fs::read_to_string(path).expect("read");
let xml_bytes = xml.as_bytes();
unsafe {
let doc_ptr = xmlReadMemory(
xml_bytes.as_ptr() as *const ::std::os::raw::c_char,
xml_bytes.len() as ::std::os::raw::c_int,
c"file.xml".as_ptr(),
std::ptr::null(),
xmlParserOption_XML_PARSE_NODICT as ::std::os::raw::c_int,
);
assert!(!doc_ptr.is_null(), "xmlReadMemory returned NULL");
let doc = Document::new_ptr(doc_ptr);
let root = doc.get_root_element().unwrap();
let sections: Vec<Node> = root
.findnodes("descendant::*[local-name()='section']")
.unwrap_or_default()
.into_iter()
.filter(|n| {
n.get_parent()
.map(|p| p.get_name() == "chapter")
.unwrap_or(false)
})
.collect();
let mut detached: Vec<Node> = sections.to_vec();
for s in detached.iter_mut() {
s.unlink_node();
}
let mut copies = Vec::new();
for (i, s) in detached.iter().enumerate() {
let c = xmlCopyNode(s.node_ptr(), 1);
eprintln!("[NODICT] dup #{} ret={:p}", i, c);
assert!(!c.is_null(), "dup #{} returned NULL even with NODICT", i);
copies.push(c);
}
for c in copies {
xmlFreeNode(c);
}
drop(detached);
drop(doc);
let _ = doc_ptr; let _ = xmlFreeDoc;
}
}
#[test]
fn _doc_allocator_repro_marker() {
}