use std::ffi::{CStr, CString};
use std::ptr;
use tinyxml2_capi::{
TX_NULL_NODE, TxDocument, TxError, TxNodeId, TxNodeType, tx_bool_attribute, tx_document_clear,
tx_document_error, tx_document_error_line, tx_document_error_name, tx_document_free,
tx_document_load_file, tx_document_new, tx_document_parse, tx_document_save_file,
tx_document_to_string, tx_document_to_string_compact, tx_double_attribute,
tx_element_attribute, tx_element_delete_attribute, tx_element_get_text, tx_element_name,
tx_element_set_attribute, tx_element_set_text, tx_first_child, tx_first_child_element,
tx_insert_after_child, tx_insert_end_child, tx_insert_first_child, tx_int_attribute,
tx_last_child, tx_new_comment, tx_new_declaration, tx_new_element, tx_new_text, tx_new_unknown,
tx_next_sibling, tx_next_sibling_element, tx_node_is_null, tx_node_line, tx_node_type,
tx_node_value, tx_parent, tx_prev_sibling, tx_printer_clear, tx_printer_close_element,
tx_printer_free, tx_printer_new, tx_printer_new_compact, tx_printer_open_element,
tx_printer_push_attribute, tx_printer_push_comment, tx_printer_push_text, tx_printer_result,
tx_query_bool_attribute, tx_query_double_attribute, tx_query_int_attribute, tx_root_element,
};
fn c(s: &str) -> CString {
CString::new(s).unwrap()
}
unsafe fn str_from_ptr(ptr: *const std::ffi::c_char) -> &'static str {
assert!(!ptr.is_null(), "unexpected null C string pointer");
unsafe { CStr::from_ptr(ptr) }.to_str().unwrap()
}
#[test]
fn doc_create_and_free() {
let doc = tx_document_new();
assert!(!doc.is_null());
let err = unsafe { tx_document_error(doc) };
assert_eq!(err, TxError::TxSuccess);
unsafe { tx_document_free(doc) };
}
#[test]
fn doc_parse_valid_xml() {
let doc = tx_document_new();
let xml = c("<root><child/></root>");
let err = unsafe { tx_document_parse(doc, xml.as_ptr()) };
assert_eq!(err, TxError::TxSuccess);
let root = unsafe { tx_root_element(doc) };
assert!(!tx_node_is_null(root));
let name_ptr = unsafe { tx_element_name(doc, root) };
assert_eq!(unsafe { str_from_ptr(name_ptr) }, "root");
unsafe { tx_document_free(doc) };
}
#[test]
fn doc_parse_invalid_xml() {
let doc = tx_document_new();
let xml = c("<root><unclosed>");
let err = unsafe { tx_document_parse(doc, xml.as_ptr()) };
assert_ne!(err, TxError::TxSuccess);
let err2 = unsafe { tx_document_error(doc) };
assert_ne!(err2, TxError::TxSuccess);
unsafe { tx_document_free(doc) };
}
#[test]
fn doc_clear_and_reparse() {
let doc = tx_document_new();
let xml1 = c("<first/>");
let err = unsafe { tx_document_parse(doc, xml1.as_ptr()) };
assert_eq!(err, TxError::TxSuccess);
let root = unsafe { tx_root_element(doc) };
let name_ptr = unsafe { tx_element_name(doc, root) };
assert_eq!(unsafe { str_from_ptr(name_ptr) }, "first");
unsafe { tx_document_clear(doc) };
let xml2 = c("<second/>");
let err2 = unsafe { tx_document_parse(doc, xml2.as_ptr()) };
assert_eq!(err2, TxError::TxSuccess);
let root2 = unsafe { tx_root_element(doc) };
let name_ptr2 = unsafe { tx_element_name(doc, root2) };
assert_eq!(unsafe { str_from_ptr(name_ptr2) }, "second");
unsafe { tx_document_free(doc) };
}
#[test]
fn factory_create_elements_and_insert() {
let doc = tx_document_new();
let root_name = c("root");
let root = unsafe { tx_new_element(doc, root_name.as_ptr()) };
assert!(!tx_node_is_null(root));
let _doc_node = unsafe { tx_first_child(doc, TX_NULL_NODE) };
let doc_id = TxNodeId {
index: 0,
generation: 0,
};
let err = unsafe { tx_insert_end_child(doc, doc_id, root) };
assert_eq!(err, TxError::TxSuccess);
let child1_name = c("alpha");
let child1 = unsafe { tx_new_element(doc, child1_name.as_ptr()) };
let err = unsafe { tx_insert_end_child(doc, root, child1) };
assert_eq!(err, TxError::TxSuccess);
let child2_name = c("beta");
let child2 = unsafe { tx_new_element(doc, child2_name.as_ptr()) };
let err = unsafe { tx_insert_first_child(doc, root, child2) };
assert_eq!(err, TxError::TxSuccess);
let first = unsafe { tx_first_child(doc, root) };
let first_name = unsafe { tx_element_name(doc, first) };
assert_eq!(unsafe { str_from_ptr(first_name) }, "beta");
let last = unsafe { tx_last_child(doc, root) };
let last_name = unsafe { tx_element_name(doc, last) };
assert_eq!(unsafe { str_from_ptr(last_name) }, "alpha");
let child3_name = c("gamma");
let child3 = unsafe { tx_new_element(doc, child3_name.as_ptr()) };
let err = unsafe { tx_insert_after_child(doc, first, child3) };
assert_eq!(err, TxError::TxSuccess);
let mid = unsafe { tx_next_sibling(doc, first) };
let mid_name = unsafe { tx_element_name(doc, mid) };
assert_eq!(unsafe { str_from_ptr(mid_name) }, "gamma");
unsafe { tx_document_free(doc) };
}
#[test]
fn factory_create_text_comment_declaration_unknown() {
let doc = tx_document_new();
let doc_id = tinyxml2_capi::TxNodeId {
index: 0,
generation: 0,
};
let root_name = c("root");
let root = unsafe { tx_new_element(doc, root_name.as_ptr()) };
let err = unsafe { tx_insert_end_child(doc, doc_id, root) };
assert_eq!(err, TxError::TxSuccess);
let text_content = c("Hello World");
let text = unsafe { tx_new_text(doc, text_content.as_ptr()) };
assert!(!tx_node_is_null(text));
let err = unsafe { tx_insert_end_child(doc, root, text) };
assert_eq!(err, TxError::TxSuccess);
let comment_content = c("this is a comment");
let comment = unsafe { tx_new_comment(doc, comment_content.as_ptr()) };
assert!(!tx_node_is_null(comment));
let err = unsafe { tx_insert_end_child(doc, root, comment) };
assert_eq!(err, TxError::TxSuccess);
let decl_content = c("xml version=\"1.0\"");
let decl = unsafe { tx_new_declaration(doc, decl_content.as_ptr()) };
assert!(!tx_node_is_null(decl));
let unknown_content = c("!ENTITY test");
let unknown = unsafe { tx_new_unknown(doc, unknown_content.as_ptr()) };
assert!(!tx_node_is_null(unknown));
let text_type = unsafe { tx_node_type(doc, text) };
assert_eq!(text_type, TxNodeType::TxNodeText);
let comment_type = unsafe { tx_node_type(doc, comment) };
assert_eq!(comment_type, TxNodeType::TxNodeComment);
let decl_type = unsafe { tx_node_type(doc, decl) };
assert_eq!(decl_type, TxNodeType::TxNodeDeclaration);
let unknown_type = unsafe { tx_node_type(doc, unknown) };
assert_eq!(unknown_type, TxNodeType::TxNodeUnknown);
unsafe { tx_document_free(doc) };
}
#[test]
fn nav_parent_child_sibling() {
let doc = tx_document_new();
let xml = c("<root><a/><b/><c/></root>");
let err = unsafe { tx_document_parse(doc, xml.as_ptr()) };
assert_eq!(err, TxError::TxSuccess);
let root = unsafe { tx_root_element(doc) };
let a = unsafe { tx_first_child(doc, root) };
assert!(!tx_node_is_null(a));
let a_name = unsafe { tx_element_name(doc, a) };
assert_eq!(unsafe { str_from_ptr(a_name) }, "a");
let c_node = unsafe { tx_last_child(doc, root) };
let c_name = unsafe { tx_element_name(doc, c_node) };
assert_eq!(unsafe { str_from_ptr(c_name) }, "c");
let b = unsafe { tx_next_sibling(doc, a) };
let b_name = unsafe { tx_element_name(doc, b) };
assert_eq!(unsafe { str_from_ptr(b_name) }, "b");
let prev = unsafe { tx_prev_sibling(doc, c_node) };
let prev_name = unsafe { tx_element_name(doc, prev) };
assert_eq!(unsafe { str_from_ptr(prev_name) }, "b");
let par = unsafe { tx_parent(doc, a) };
let par_name = unsafe { tx_element_name(doc, par) };
assert_eq!(unsafe { str_from_ptr(par_name) }, "root");
let none = unsafe { tx_prev_sibling(doc, a) };
assert!(tx_node_is_null(none));
let none2 = unsafe { tx_next_sibling(doc, c_node) };
assert!(tx_node_is_null(none2));
unsafe { tx_document_free(doc) };
}
#[test]
fn nav_first_child_element_with_name_filter() {
let doc = tx_document_new();
let xml = c("<root><item id=\"1\"/><other/><item id=\"2\"/></root>");
let err = unsafe { tx_document_parse(doc, xml.as_ptr()) };
assert_eq!(err, TxError::TxSuccess);
let root = unsafe { tx_root_element(doc) };
let first_any = unsafe { tx_first_child_element(doc, root, ptr::null()) };
assert!(!tx_node_is_null(first_any));
let first_name = unsafe { tx_element_name(doc, first_any) };
assert_eq!(unsafe { str_from_ptr(first_name) }, "item");
let filter = c("other");
let other = unsafe { tx_first_child_element(doc, root, filter.as_ptr()) };
assert!(!tx_node_is_null(other));
let other_name = unsafe { tx_element_name(doc, other) };
assert_eq!(unsafe { str_from_ptr(other_name) }, "other");
let item_filter = c("item");
let second_item = unsafe { tx_next_sibling_element(doc, first_any, item_filter.as_ptr()) };
assert!(!tx_node_is_null(second_item));
let id_attr = c("id");
let id_ptr = unsafe { tx_element_attribute(doc, second_item, id_attr.as_ptr()) };
assert_eq!(unsafe { str_from_ptr(id_ptr) }, "2");
let nope = c("nonexistent");
let missing = unsafe { tx_first_child_element(doc, root, nope.as_ptr()) };
assert!(tx_node_is_null(missing));
unsafe { tx_document_free(doc) };
}
#[test]
fn nav_root_element() {
let doc = tx_document_new();
let root = unsafe { tx_root_element(doc) };
assert!(tx_node_is_null(root));
let xml = c("<hello/>");
let _ = unsafe { tx_document_parse(doc, xml.as_ptr()) };
let root = unsafe { tx_root_element(doc) };
assert!(!tx_node_is_null(root));
let name_ptr = unsafe { tx_element_name(doc, root) };
assert_eq!(unsafe { str_from_ptr(name_ptr) }, "hello");
unsafe { tx_document_free(doc) };
}
#[test]
fn attr_set_and_get() {
let doc = tx_document_new();
let xml = c("<root/>");
let _ = unsafe { tx_document_parse(doc, xml.as_ptr()) };
let root = unsafe { tx_root_element(doc) };
let attr_name = c("color");
let attr_val = c("blue");
let err = unsafe { tx_element_set_attribute(doc, root, attr_name.as_ptr(), attr_val.as_ptr()) };
assert_eq!(err, TxError::TxSuccess);
let ptr = unsafe { tx_element_attribute(doc, root, attr_name.as_ptr()) };
assert_eq!(unsafe { str_from_ptr(ptr) }, "blue");
let attr_val2 = c("red");
let err =
unsafe { tx_element_set_attribute(doc, root, attr_name.as_ptr(), attr_val2.as_ptr()) };
assert_eq!(err, TxError::TxSuccess);
let ptr2 = unsafe { tx_element_attribute(doc, root, attr_name.as_ptr()) };
assert_eq!(unsafe { str_from_ptr(ptr2) }, "red");
let missing = c("nope");
let ptr3 = unsafe { tx_element_attribute(doc, root, missing.as_ptr()) };
assert!(ptr3.is_null());
unsafe { tx_document_free(doc) };
}
#[test]
fn attr_delete() {
let doc = tx_document_new();
let xml = c("<root keep=\"yes\" remove=\"me\"/>");
let _ = unsafe { tx_document_parse(doc, xml.as_ptr()) };
let root = unsafe { tx_root_element(doc) };
let remove_name = c("remove");
let err = unsafe { tx_element_delete_attribute(doc, root, remove_name.as_ptr()) };
assert_eq!(err, TxError::TxSuccess);
let ptr = unsafe { tx_element_attribute(doc, root, remove_name.as_ptr()) };
assert!(ptr.is_null());
let keep_name = c("keep");
let ptr2 = unsafe { tx_element_attribute(doc, root, keep_name.as_ptr()) };
assert_eq!(unsafe { str_from_ptr(ptr2) }, "yes");
unsafe { tx_document_free(doc) };
}
#[test]
fn attr_typed_int_bool_double() {
let doc = tx_document_new();
let xml = c("<item count=\"42\" active=\"true\" ratio=\"1.25\"/>");
let _ = unsafe { tx_document_parse(doc, xml.as_ptr()) };
let root = unsafe { tx_root_element(doc) };
let count_name = c("count");
let mut int_val: std::ffi::c_int = 0;
let err = unsafe { tx_query_int_attribute(doc, root, count_name.as_ptr(), &raw mut int_val) };
assert_eq!(err, TxError::TxSuccess);
assert_eq!(int_val, 42);
let active_name = c("active");
let mut bool_val: bool = false;
let err =
unsafe { tx_query_bool_attribute(doc, root, active_name.as_ptr(), &raw mut bool_val) };
assert_eq!(err, TxError::TxSuccess);
assert!(bool_val);
let ratio_name = c("ratio");
let mut dbl_val: std::ffi::c_double = 0.0;
let err =
unsafe { tx_query_double_attribute(doc, root, ratio_name.as_ptr(), &raw mut dbl_val) };
assert_eq!(err, TxError::TxSuccess);
assert!((dbl_val - 1.25).abs() < 1e-10);
let int_v = unsafe { tx_int_attribute(doc, root, count_name.as_ptr(), -1) };
assert_eq!(int_v, 42);
let bool_v = unsafe { tx_bool_attribute(doc, root, active_name.as_ptr(), false) };
assert!(bool_v);
let dbl_v = unsafe { tx_double_attribute(doc, root, ratio_name.as_ptr(), 0.0) };
assert!((dbl_v - 1.25).abs() < 1e-10);
let missing = c("missing");
let def_int = unsafe { tx_int_attribute(doc, root, missing.as_ptr(), -99) };
assert_eq!(def_int, -99);
let def_bool = unsafe { tx_bool_attribute(doc, root, missing.as_ptr(), true) };
assert!(def_bool);
let def_dbl = unsafe { tx_double_attribute(doc, root, missing.as_ptr(), 9.9) };
assert!((def_dbl - 9.9).abs() < f64::EPSILON);
unsafe { tx_document_free(doc) };
}
#[test]
fn text_get_and_set() {
let doc = tx_document_new();
let xml = c("<msg>Hello</msg>");
let _ = unsafe { tx_document_parse(doc, xml.as_ptr()) };
let root = unsafe { tx_root_element(doc) };
let ptr = unsafe { tx_element_get_text(doc, root) };
assert_eq!(unsafe { str_from_ptr(ptr) }, "Hello");
let new_text = c("Goodbye");
let err = unsafe { tx_element_set_text(doc, root, new_text.as_ptr()) };
assert_eq!(err, TxError::TxSuccess);
let ptr2 = unsafe { tx_element_get_text(doc, root) };
assert_eq!(unsafe { str_from_ptr(ptr2) }, "Goodbye");
unsafe { tx_document_free(doc) };
}
#[test]
fn text_get_null_for_no_text() {
let doc = tx_document_new();
let xml = c("<empty/>");
let _ = unsafe { tx_document_parse(doc, xml.as_ptr()) };
let root = unsafe { tx_root_element(doc) };
let ptr = unsafe { tx_element_get_text(doc, root) };
assert!(ptr.is_null());
unsafe { tx_document_free(doc) };
}
#[test]
fn printer_pretty() {
let printer = tx_printer_new();
assert!(!printer.is_null());
let root_name = c("book");
unsafe { tx_printer_open_element(printer, root_name.as_ptr()) };
let attr_name = c("lang");
let attr_val = c("en");
unsafe { tx_printer_push_attribute(printer, attr_name.as_ptr(), attr_val.as_ptr()) };
let text = c("Title");
unsafe { tx_printer_push_text(printer, text.as_ptr()) };
unsafe { tx_printer_close_element(printer) };
let result_ptr = unsafe { tx_printer_result(printer) };
let result = unsafe { str_from_ptr(result_ptr) };
assert!(result.contains("<book"));
assert!(result.contains("lang=\"en\""));
assert!(result.contains("Title"));
assert!(result.contains("</book>"));
unsafe { tx_printer_clear(printer) };
let cleared_ptr = unsafe { tx_printer_result(printer) };
let cleared = unsafe { str_from_ptr(cleared_ptr) };
assert!(
cleared.is_empty(),
"printer should be empty after clear, got: {cleared:?}"
);
unsafe { tx_printer_free(printer) };
}
#[test]
fn printer_compact() {
let printer = tx_printer_new_compact();
assert!(!printer.is_null());
let elem = c("item");
unsafe { tx_printer_open_element(printer, elem.as_ptr()) };
let comment = c("note");
unsafe { tx_printer_push_comment(printer, comment.as_ptr()) };
let text = c("data");
unsafe { tx_printer_push_text(printer, text.as_ptr()) };
unsafe { tx_printer_close_element(printer) };
let result_ptr = unsafe { tx_printer_result(printer) };
let result = unsafe { str_from_ptr(result_ptr) };
assert!(result.contains("<item>"));
assert!(result.contains("<!--note-->"));
assert!(result.contains("data"));
assert!(result.contains("</item>"));
assert!(
!result.contains("\n "),
"compact output should not have indentation"
);
unsafe { tx_printer_free(printer) };
}
#[test]
fn node_type_and_value() {
let doc = tx_document_new();
let xml = c("<root>text<!-- comment --></root>");
let _ = unsafe { tx_document_parse(doc, xml.as_ptr()) };
let root = unsafe { tx_root_element(doc) };
let root_type = unsafe { tx_node_type(doc, root) };
assert_eq!(root_type, TxNodeType::TxNodeElement);
let root_val = unsafe { tx_node_value(doc, root) };
assert_eq!(unsafe { str_from_ptr(root_val) }, "root");
let text_node = unsafe { tx_first_child(doc, root) };
let text_type = unsafe { tx_node_type(doc, text_node) };
assert_eq!(text_type, TxNodeType::TxNodeText);
let text_val = unsafe { tx_node_value(doc, text_node) };
assert_eq!(unsafe { str_from_ptr(text_val) }, "text");
let comment_node = unsafe { tx_next_sibling(doc, text_node) };
let comment_type = unsafe { tx_node_type(doc, comment_node) };
assert_eq!(comment_type, TxNodeType::TxNodeComment);
let comment_val = unsafe { tx_node_value(doc, comment_node) };
assert_eq!(unsafe { str_from_ptr(comment_val) }, " comment ");
unsafe { tx_document_free(doc) };
}
#[test]
fn node_is_null_and_line() {
assert!(tx_node_is_null(TX_NULL_NODE));
let doc = tx_document_new();
let xml = c("<root/>");
let _ = unsafe { tx_document_parse(doc, xml.as_ptr()) };
let root = unsafe { tx_root_element(doc) };
assert!(!tx_node_is_null(root));
let line = unsafe { tx_node_line(doc, root) };
assert!(line >= 1, "line should be >= 1 for a parsed node");
unsafe { tx_document_free(doc) };
}
#[test]
fn null_document_pointer() {
let null_doc: *mut TxDocument = ptr::null_mut();
let err = unsafe { tx_document_error(null_doc) };
assert_eq!(err, TxError::TxErrorInvalidNodeId);
let line = unsafe { tx_document_error_line(null_doc) };
assert_eq!(line, 0);
let name = unsafe { tx_document_error_name(null_doc) };
assert!(name.is_null());
let root = unsafe { tx_root_element(null_doc) };
assert!(tx_node_is_null(root));
let to_str = unsafe { tx_document_to_string(null_doc) };
assert!(to_str.is_null());
let to_str_c = unsafe { tx_document_to_string_compact(null_doc) };
assert!(to_str_c.is_null());
unsafe { tx_document_free(null_doc) };
unsafe { tx_document_clear(null_doc) };
let xml = c("<x/>");
let err = unsafe { tx_document_parse(null_doc, xml.as_ptr()) };
assert_ne!(err, TxError::TxSuccess);
}
#[test]
fn null_string_pointer() {
let doc = tx_document_new();
let err = unsafe { tx_document_parse(doc, ptr::null()) };
assert_ne!(err, TxError::TxSuccess);
let node = unsafe { tx_new_element(doc, ptr::null()) };
assert!(tx_node_is_null(node));
let node = unsafe { tx_new_text(doc, ptr::null()) };
assert!(tx_node_is_null(node));
let node = unsafe { tx_new_comment(doc, ptr::null()) };
assert!(tx_node_is_null(node));
let err = unsafe { tx_document_load_file(doc, ptr::null()) };
assert_ne!(err, TxError::TxSuccess);
let err = unsafe { tx_document_save_file(doc, ptr::null()) };
assert_ne!(err, TxError::TxSuccess);
unsafe { tx_document_free(doc) };
}
#[test]
fn serialization_to_string() {
let doc = tx_document_new();
let xml = c("<root><child/></root>");
let _ = unsafe { tx_document_parse(doc, xml.as_ptr()) };
let ptr = unsafe { tx_document_to_string(doc) };
assert!(!ptr.is_null());
let result = unsafe { str_from_ptr(ptr) };
assert!(result.contains("<root>"));
assert!(result.contains("<child/>"));
assert!(result.contains("</root>"));
unsafe { tx_document_free(doc) };
}
#[test]
fn serialization_to_string_compact() {
let doc = tx_document_new();
let xml = c("<root><a/><b/></root>");
let _ = unsafe { tx_document_parse(doc, xml.as_ptr()) };
let ptr = unsafe { tx_document_to_string_compact(doc) };
assert!(!ptr.is_null());
let result = unsafe { str_from_ptr(ptr) };
assert!(result.contains("<root>"));
assert!(result.contains("<a/>"));
assert!(result.contains("<b/>"));
assert!(result.contains("</root>"));
assert!(
!result.contains("\n "),
"compact output should not have indentation, got: {result:?}"
);
unsafe { tx_document_free(doc) };
}