1use core::ffi::c_void;
30use core::ptr;
31use std::os::raw::{c_char, c_int};
32
33use crate::abi::allocator;
34use crate::abi::structs::*;
35use crate::abi::types::xmlDocProperties::XML_DOC_XINCLUDE;
36use crate::abi::types::xmlElementType::*;
37use crate::abi::types::*;
38use crate::xml::string::*;
39use crate::xml::tree;
40use crate::xml::xpointer;
41
42const XINCLUDE_NS: &[u8] = b"http://www.w3.org/2001/XInclude\0";
48
49#[allow(dead_code)]
51const XINCLUDE_INCLUDE: &[u8] = b"include\0";
52
53#[allow(dead_code)]
55const XINCLUDE_FALLBACK: &[u8] = b"fallback\0";
56
57const ATTR_HREF: &[u8] = b"href\0";
59
60const ATTR_PARSE: &[u8] = b"parse\0";
62
63const ATTR_XPOINTER: &[u8] = b"xpointer\0";
65
66const ATTR_ENCODING: &[u8] = b"encoding\0";
68
69const ATTR_ACCEPT: &[u8] = b"accept\0";
71
72const ATTR_ACCEPT_LANGUAGE: &[u8] = b"accept-language\0";
74
75#[allow(dead_code)]
81const XINCLUDE_SUCCESS: c_int = 0;
82
83const XINCLUDE_FAILURE: c_int = -1;
85
86const XINCLUDE_NO_NODES: c_int = 0;
88
89#[allow(dead_code)]
95const XML_XINCLUDE_NO_INCLUDE: c_int = 0;
96
97pub unsafe fn xinclude_process(doc: *mut _xmlDoc) -> c_int {
110 if doc.is_null() {
111 return XINCLUDE_FAILURE;
112 }
113
114 let mut visited: Vec<Vec<u8>> = Vec::new();
116
117 let count = unsafe { process_doc(doc, &mut visited) };
118
119 if count > 0 {
120 unsafe { mark_doc_xinclude_processed(doc) };
121 }
122
123 count
124}
125
126pub unsafe fn xinclude_process_flags(doc: *mut _xmlDoc, flags: c_int) -> c_int {
136 if doc.is_null() {
137 return XINCLUDE_FAILURE;
138 }
139
140 if flags & XML_PARSE_NOXINCNODE != 0 {
142 return XINCLUDE_NO_NODES;
143 }
144
145 let mut visited: Vec<Vec<u8>> = Vec::new();
147
148 let count = unsafe { process_doc(doc, &mut visited) };
149
150 if count > 0 {
151 unsafe { mark_doc_xinclude_processed(doc) };
152 }
153
154 count
155}
156
157unsafe fn mark_doc_xinclude_processed(doc: *mut _xmlDoc) {
167 unsafe {
168 let d = &mut *doc;
169 d.properties |= XML_DOC_XINCLUDE as c_int;
170 }
171}
172
173unsafe fn process_doc(doc: *mut _xmlDoc, visited: &mut Vec<Vec<u8>>) -> c_int {
180 let mut count: c_int = 0;
181
182 let root = unsafe { find_root_element(doc) };
184 if root.is_null() {
185 return XINCLUDE_NO_NODES;
186 }
187
188 unsafe {
190 count += process_node_tree(root, doc, visited);
191 }
192
193 count
194}
195
196unsafe fn process_node_tree(
204 node: *mut _xmlNode,
205 doc: *mut _xmlDoc,
206 visited: &mut Vec<Vec<u8>>,
207) -> c_int {
208 if node.is_null() {
209 return 0;
210 }
211
212 let mut count: c_int = 0;
213
214 let node_type = unsafe { (*node).type_ };
219 if node_type == XML_XINCLUDE_START as c_int || node_type == XML_XINCLUDE_END as c_int {
220 if node_type == XML_XINCLUDE_START as c_int {
223 let mut child = unsafe { (*node).children };
224 while !child.is_null() {
225 count += unsafe { process_node_tree(child, doc, visited) };
226 child = unsafe { (*child).next };
227 }
228 }
229 return count;
230 }
231
232 let mut children: Vec<*mut _xmlNode> = Vec::new();
235 let mut child = unsafe { (*node).children };
236 while !child.is_null() {
237 children.push(child);
238 child = unsafe { (*child).next };
239 }
240
241 for child_node in children {
242 if unsafe { is_xinclude_element(child_node) } {
244 let processed = unsafe { process_single_include(child_node, doc, visited) };
245 if processed >= 0 {
246 count += processed;
247 } else {
248 count = -1; }
250 } else {
251 let child_type = unsafe { (*child_node).type_ };
253 if child_type == XML_ELEMENT_NODE as c_int
254 || child_type == XML_DOCUMENT_NODE as c_int
255 || child_type == XML_DOCUMENT_FRAG_NODE as c_int
256 || child_type == XML_XINCLUDE_START as c_int
257 {
258 count += unsafe { process_node_tree(child_node, doc, visited) };
259 }
260 }
261 }
262
263 count
264}
265
266unsafe fn is_xinclude_element(node: *mut _xmlNode) -> bool {
276 if node.is_null() {
277 return false;
278 }
279
280 let n = unsafe { &*node };
281 if n.type_ != XML_ELEMENT_NODE as c_int {
282 return false;
283 }
284
285 if n.name.is_null() {
286 return false;
287 }
288
289 let has_xinclude_ns = if !n.ns.is_null() {
291 let ns = unsafe { &*n.ns };
292 !ns.href.is_null()
293 && unsafe { xml_str_equal(ns.href, XINCLUDE_NS.as_ptr() as *const xmlChar) }
294 } else {
295 check_namespace_declaration(node, XINCLUDE_NS.as_ptr() as *const xmlChar)
299 };
300
301 if !has_xinclude_ns {
302 return false;
303 }
304
305 let name_bytes = unsafe { xmlstr_to_bytes(n.name) };
307 let local_name = if let Some(pos) = name_bytes.iter().position(|&b| b == b':') {
308 &name_bytes[pos + 1..]
309 } else {
310 name_bytes
311 };
312
313 local_name == b"include"
314}
315
316unsafe fn is_fallback_element(node: *mut _xmlNode) -> bool {
322 if node.is_null() {
323 return false;
324 }
325
326 let n = unsafe { &*node };
327 if n.type_ != XML_ELEMENT_NODE as c_int {
328 return false;
329 }
330
331 if n.name.is_null() {
332 return false;
333 }
334
335 let has_xinclude_ns = if !n.ns.is_null() {
337 let ns = unsafe { &*n.ns };
338 !ns.href.is_null()
339 && unsafe { xml_str_equal(ns.href, XINCLUDE_NS.as_ptr() as *const xmlChar) }
340 } else {
341 check_namespace_declaration(node, XINCLUDE_NS.as_ptr() as *const xmlChar)
342 };
343
344 if !has_xinclude_ns {
345 return false;
346 }
347
348 let name_bytes = unsafe { xmlstr_to_bytes(n.name) };
350 let local_name = if let Some(pos) = name_bytes.iter().position(|&b| b == b':') {
351 &name_bytes[pos + 1..]
352 } else {
353 name_bytes
354 };
355
356 local_name == b"fallback"
357}
358
359unsafe fn process_single_include(
367 include_node: *mut _xmlNode,
368 doc: *mut _xmlDoc,
369 visited: &mut Vec<Vec<u8>>,
370) -> c_int {
371 let href = unsafe { tree::get_prop(include_node, ATTR_HREF.as_ptr() as *const xmlChar) };
373
374 if href.is_null() {
376 return unsafe { apply_fallback(include_node, doc, visited) };
377 }
378
379 let href_str = unsafe { xmlstr_to_bytes(href) };
380
381 if visited.iter().any(|v| v.as_slice() == href_str) {
383 allocator::xmlFreeImpl(href as *mut c_void);
384 return unsafe { apply_fallback(include_node, doc, visited) };
385 }
386
387 let parse_attr = unsafe { tree::get_prop(include_node, ATTR_PARSE.as_ptr() as *const xmlChar) };
389 let is_text_mode = if !parse_attr.is_null() {
390 let parse_str = unsafe { xmlstr_to_bytes(parse_attr) };
391 let result = parse_str == b"text";
392 allocator::xmlFreeImpl(parse_attr as *mut c_void);
393 result
394 } else {
395 false
396 };
397
398 let xpointer_attr =
400 unsafe { tree::get_prop(include_node, ATTR_XPOINTER.as_ptr() as *const xmlChar) };
401
402 let accept_attr =
404 unsafe { tree::get_prop(include_node, ATTR_ACCEPT.as_ptr() as *const xmlChar) };
405
406 let accept_language_attr = unsafe {
408 tree::get_prop(
409 include_node,
410 ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar,
411 )
412 };
413
414 visited.push(href_str.to_vec());
416
417 let result = if is_text_mode {
418 unsafe { process_text_include(include_node, doc, href, accept_attr, visited) }
419 } else {
420 unsafe { process_xml_include(include_node, doc, href, xpointer_attr, visited) }
421 };
422
423 visited.pop();
425
426 allocator::xmlFreeImpl(href as *mut c_void);
428
429 if !parse_attr.is_null() {
430 allocator::xmlFreeImpl(parse_attr as *mut c_void);
431 }
432 if !xpointer_attr.is_null() {
433 allocator::xmlFreeImpl(xpointer_attr as *mut c_void);
434 }
435 if !accept_attr.is_null() {
436 allocator::xmlFreeImpl(accept_attr as *mut c_void);
437 }
438 if !accept_language_attr.is_null() {
439 allocator::xmlFreeImpl(accept_language_attr as *mut c_void);
440 }
441
442 match result {
443 Ok(processed) => processed,
444 Err(()) => unsafe { apply_fallback(include_node, doc, visited) },
445 }
446}
447
448unsafe fn process_text_include(
457 include_node: *mut _xmlNode,
458 doc: *mut _xmlDoc,
459 href: *mut xmlChar,
460 _accept: *mut xmlChar,
461 _visited: &mut Vec<Vec<u8>>,
462) -> Result<c_int, ()> {
463 let content = unsafe { io_read_file(href) };
465
466 if content.is_null() {
467 return Err(());
468 }
469
470 let encoding_attr =
472 unsafe { tree::get_prop(include_node, ATTR_ENCODING.as_ptr() as *const xmlChar) };
473
474 let text_node = unsafe { tree::new_text(content as *const xmlChar) };
476 if text_node.is_null() {
477 allocator::xmlFreeImpl(content as *mut c_void);
478 if !encoding_attr.is_null() {
479 allocator::xmlFreeImpl(encoding_attr as *mut c_void);
480 }
481 return Err(());
482 }
483
484 unsafe { replace_node_with_content(include_node, text_node, doc) };
486
487 allocator::xmlFreeImpl(content as *mut c_void);
488 if !encoding_attr.is_null() {
489 allocator::xmlFreeImpl(encoding_attr as *mut c_void);
490 }
491
492 Ok(1)
493}
494
495unsafe fn process_xml_include(
504 include_node: *mut _xmlNode,
505 doc: *mut _xmlDoc,
506 href: *mut xmlChar,
507 xpointer_attr: *mut xmlChar,
508 visited: &mut Vec<Vec<u8>>,
509) -> Result<c_int, ()> {
510 let included_doc = unsafe { parse_xml_document(href) };
512 if included_doc.is_null() {
513 return Err(());
514 }
515
516 let result = if !xpointer_attr.is_null() {
517 let xptr_str = unsafe { xmlstr_to_bytes(xpointer_attr) };
519 let xptr_utf8 = unsafe { std::str::from_utf8_unchecked(xptr_str) };
520 unsafe { include_via_xpointer(include_node, doc, included_doc, xptr_utf8, visited) }
521 } else {
522 unsafe { include_document_element(include_node, doc, included_doc, visited) }
524 };
525
526 let _ = unsafe { process_doc(included_doc, visited) };
528
529 unsafe { tree::free_doc(included_doc) };
532
533 result
534}
535
536unsafe fn include_document_element(
542 include_node: *mut _xmlNode,
543 doc: *mut _xmlDoc,
544 included_doc: *mut _xmlDoc,
545 _visited: &mut Vec<Vec<u8>>,
546) -> Result<c_int, ()> {
547 let root = unsafe { find_root_element(included_doc) };
548 if root.is_null() {
549 return Err(());
550 }
551
552 let copy = unsafe { tree::copy_node(root, 1) };
554 if copy.is_null() {
555 return Err(());
556 }
557
558 unsafe { set_doc_recursive(copy, doc) };
560
561 unsafe { replace_node_with_content(include_node, copy, doc) };
563
564 Ok(1)
565}
566
567unsafe fn include_via_xpointer(
573 include_node: *mut _xmlNode,
574 doc: *mut _xmlDoc,
575 included_doc: *mut _xmlDoc,
576 xpointer_expr: &str,
577 _visited: &mut Vec<Vec<u8>>,
578) -> Result<c_int, ()> {
579 let target = unsafe { xpointer::xptr_eval(xpointer_expr, included_doc) };
581
582 match target {
583 Some(target_node) => {
584 let copy = unsafe { tree::copy_node(target_node, 1) };
586 if copy.is_null() {
587 return Err(());
588 }
589
590 unsafe { set_doc_recursive(copy, doc) };
592
593 unsafe { replace_node_with_content(include_node, copy, doc) };
595
596 Ok(1)
597 }
598 None => Err(()),
599 }
600}
601
602unsafe fn apply_fallback(
610 include_node: *mut _xmlNode,
611 doc: *mut _xmlDoc,
612 visited: &mut Vec<Vec<u8>>,
613) -> c_int {
614 if include_node.is_null() {
615 return XINCLUDE_FAILURE;
616 }
617
618 let fallback = unsafe { find_fallback_child(include_node) };
620 if fallback.is_null() {
621 return 0; }
623
624 let mut fallback_children: Vec<*mut _xmlNode> = Vec::new();
626 let mut child = unsafe { (*fallback).children };
627 while !child.is_null() {
628 let next = unsafe { (*child).next };
629 fallback_children.push(child);
630 child = next;
631 }
632
633 if fallback_children.is_empty() {
634 unsafe { remove_node(include_node) };
636 return 1;
637 }
638
639 let parent = unsafe { (*include_node).parent };
641 if parent.is_null() {
642 return XINCLUDE_FAILURE;
643 }
644
645 let mut first_inserted: *mut _xmlNode = ptr::null_mut();
646 let mut last_inserted: *mut _xmlNode = ptr::null_mut();
647
648 for fb_child in &fallback_children {
649 let copy = unsafe { tree::copy_node(*fb_child, 1) };
650 if copy.is_null() {
651 continue;
652 }
653 unsafe { set_doc_recursive(copy, doc) };
654
655 unsafe {
657 let inserted = tree::add_sibling_before(include_node, copy);
658 if !inserted.is_null() {
659 if first_inserted.is_null() {
660 first_inserted = inserted;
661 }
662 last_inserted = inserted;
663 }
664 }
665 }
666
667 if !first_inserted.is_null() {
669 let mut cur = first_inserted;
670 loop {
671 unsafe {
672 let _ = process_node_tree(cur, doc, visited);
673 }
674 if cur == last_inserted {
675 break;
676 }
677 cur = unsafe { (*cur).next };
678 if cur.is_null() {
679 break;
680 }
681 }
682 }
683
684 unsafe { remove_node(include_node) };
686
687 1
688}
689
690unsafe fn find_fallback_child(node: *mut _xmlNode) -> *mut _xmlNode {
696 if node.is_null() {
697 return ptr::null_mut();
698 }
699
700 let mut child = unsafe { (*node).children };
701 while !child.is_null() {
702 if unsafe { is_fallback_element(child) } {
703 return child;
704 }
705 child = unsafe { (*child).next };
706 }
707
708 ptr::null_mut()
709}
710
711unsafe fn replace_node_with_content(
717 old_node: *mut _xmlNode,
718 new_content: *mut _xmlNode,
719 _doc: *mut _xmlDoc,
720) {
721 if old_node.is_null() || new_content.is_null() {
722 return;
723 }
724
725 let parent = unsafe { (*old_node).parent };
726 if parent.is_null() {
727 unsafe {
730 tree::add_sibling(old_node, new_content);
731 tree::unlink_node(old_node);
732 tree::free_node(old_node);
733 }
734 return;
735 }
736
737 unsafe {
739 tree::add_sibling_before(old_node, new_content);
740 tree::unlink_node(old_node);
741 tree::free_node(old_node);
742 }
743}
744
745unsafe fn remove_node(node: *mut _xmlNode) {
751 if node.is_null() {
752 return;
753 }
754 unsafe {
755 tree::unlink_node(node);
756 tree::free_node(node);
757 }
758}
759
760unsafe fn io_read_file(filename: *const xmlChar) -> *mut xmlChar {
768 if filename.is_null() {
769 return ptr::null_mut();
770 }
771
772 let c_filename = match std::ffi::CString::new(unsafe { xmlstr_to_bytes(filename) }) {
774 Ok(s) => s,
775 Err(_) => return ptr::null_mut(),
776 };
777
778 let fd = unsafe { libc::open(c_filename.as_ptr(), libc::O_RDONLY) };
779 if fd < 0 {
780 return ptr::null_mut();
781 }
782
783 let mut data = Vec::new();
784 let mut buf = [0u8; 4096];
785
786 loop {
787 let ret = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut c_void, buf.len()) };
788 if ret < 0 {
789 unsafe { libc::close(fd) };
790 return ptr::null_mut();
791 }
792 if ret == 0 {
793 break;
794 }
795 data.extend_from_slice(&buf[..ret as usize]);
796 }
797
798 unsafe { libc::close(fd) };
799
800 if data.is_empty() {
801 return ptr::null_mut();
802 }
803
804 let result = unsafe { allocator::xmlMallocImpl(data.len() + 1) as *mut xmlChar };
806 if result.is_null() {
807 return ptr::null_mut();
808 }
809
810 unsafe {
811 ptr::copy_nonoverlapping(data.as_ptr(), result, data.len());
812 *result.add(data.len()) = 0; }
814
815 result
816}
817
818unsafe fn parse_xml_document(filename: *const xmlChar) -> *mut _xmlDoc {
826 if filename.is_null() {
827 return ptr::null_mut();
828 }
829
830 let content = unsafe { io_read_file(filename) };
832 if content.is_null() {
833 return ptr::null_mut();
834 }
835
836 let content_bytes = unsafe { xmlstr_to_bytes(content) };
837 let size = content_bytes.len() as c_int;
838
839 let doc = unsafe {
841 crate::abi::exports_xml2::xmlReadMemory(
842 content as *const c_char,
843 size,
844 filename as *const c_char,
845 ptr::null(), 0, )
848 };
849
850 allocator::xmlFreeImpl(content as *mut c_void);
851
852 doc
853}
854
855unsafe fn find_root_element(doc: *mut _xmlDoc) -> *mut _xmlNode {
861 if doc.is_null() {
862 return ptr::null_mut();
863 }
864
865 let mut child = unsafe { (*doc).children };
866 while !child.is_null() {
867 let node_type = unsafe { (*child).type_ };
868 if node_type == XML_ELEMENT_NODE as c_int {
869 return child;
870 }
871 child = unsafe { (*child).next };
872 }
873
874 ptr::null_mut()
875}
876
877unsafe fn set_doc_recursive(node: *mut _xmlNode, doc: *mut _xmlDoc) {
884 if node.is_null() {
885 return;
886 }
887
888 unsafe {
889 (*node).doc = doc;
890 }
891
892 let mut child = unsafe { (*node).children };
894 while !child.is_null() {
895 unsafe { set_doc_recursive(child, doc) };
896 child = unsafe { (*child).next };
897 }
898
899 let mut prop = unsafe { (*node).properties };
901 while !prop.is_null() {
902 unsafe {
903 (*prop).doc = doc;
904 if !(*prop).children.is_null() {
905 set_doc_recursive((*prop).children, doc);
906 }
907 }
908 prop = unsafe { (*prop).next };
909 }
910}
911
912unsafe fn check_namespace_declaration(node: *mut _xmlNode, ns_uri: *const xmlChar) -> bool {
920 if node.is_null() {
921 return false;
922 }
923
924 let mut cur: *mut _xmlNode = node;
925 while !cur.is_null() {
926 let n = unsafe { &*cur };
927 let mut ns_def = n.nsDef;
928 while !ns_def.is_null() {
929 let ns = unsafe { &*ns_def };
930 if !ns.href.is_null() && unsafe { xml_str_equal(ns.href, ns_uri) } {
931 return true;
932 }
933 ns_def = ns.next;
934 }
935 cur = n.parent;
936 }
937
938 false
939}
940
941unsafe fn xml_str_equal(a: *const xmlChar, b: *const xmlChar) -> bool {
947 if a.is_null() && b.is_null() {
948 return true;
949 }
950 if a.is_null() || b.is_null() {
951 return false;
952 }
953 unsafe { crate::abi::exports_xml2::xmlStrEqual(a, b) != 0 }
954}
955
956#[cfg(test)]
961mod tests {
962 use super::*;
963 use crate::abi::allocator;
964
965 use crate::xml::tree;
966 use std::os::raw::{c_char, c_int};
967
968 #[allow(dead_code)]
974 unsafe fn create_doc_from_xml(xml: &[u8]) -> *mut _xmlDoc {
975 let doc = unsafe {
976 crate::abi::exports_xml2::xmlReadMemory(
977 xml.as_ptr() as *const c_char,
978 xml.len() as c_int,
979 ptr::null(),
980 ptr::null(),
981 0,
982 )
983 };
984 if doc.is_null() {
985 return ptr::null_mut();
986 }
987 doc
988 }
989
990 unsafe fn create_simple_doc() -> *mut _xmlDoc {
992 let doc = tree::new_doc(ptr::null());
993 assert!(!doc.is_null(), "Failed to create doc");
994
995 let root = tree::new_child(
996 doc as *mut _xmlNode,
997 ptr::null_mut(),
998 c"root".as_ptr() as *const xmlChar,
999 );
1000 assert!(!root.is_null(), "Failed to create root");
1001
1002 doc
1003 }
1004
1005 unsafe fn create_ns(
1007 node: *mut _xmlNode,
1008 prefix: *const xmlChar,
1009 href: *const xmlChar,
1010 ) -> *mut _xmlNs {
1011 tree::new_ns(node, href, prefix)
1012 }
1013
1014 unsafe fn create_doc_with_xinclude_ns() -> (*mut _xmlDoc, *mut _xmlNode) {
1016 let doc = tree::new_doc(ptr::null());
1017 assert!(!doc.is_null());
1018 let root = tree::new_child(
1019 doc as *mut _xmlNode,
1020 ptr::null_mut(),
1021 c"root".as_ptr() as *const xmlChar,
1022 );
1023 assert!(!root.is_null());
1024 create_ns(
1025 root,
1026 c"xi".as_ptr() as *const xmlChar,
1027 XINCLUDE_NS.as_ptr() as *const xmlChar,
1028 );
1029 (doc, root)
1030 }
1031
1032 unsafe fn create_include_child(
1034 parent: *mut _xmlNode,
1035 href: Option<&[u8]>,
1036 parse: Option<&[u8]>,
1037 ) -> *mut _xmlNode {
1038 let ns = create_ns(
1039 parent,
1040 c"xi".as_ptr() as *const xmlChar,
1041 XINCLUDE_NS.as_ptr() as *const xmlChar,
1042 );
1043 let elem = tree::new_child(parent, ns, c"include".as_ptr() as *const xmlChar);
1044 if let Some(h) = href {
1045 let h_str = crate::xml::string::bytes_to_xmlstr(h);
1046 tree::set_prop(elem, ATTR_HREF.as_ptr() as *const xmlChar, h_str);
1047 allocator::xmlFreeImpl(h_str as *mut c_void);
1048 }
1049 if let Some(p) = parse {
1050 let p_str = crate::xml::string::bytes_to_xmlstr(p);
1051 tree::set_prop(elem, ATTR_PARSE.as_ptr() as *const xmlChar, p_str);
1052 allocator::xmlFreeImpl(p_str as *mut c_void);
1053 }
1054 elem
1055 }
1056
1057 unsafe fn create_fallback_child(parent: *mut _xmlNode) -> *mut _xmlNode {
1059 let ns = create_ns(
1060 parent,
1061 c"xi".as_ptr() as *const xmlChar,
1062 XINCLUDE_NS.as_ptr() as *const xmlChar,
1063 );
1064 tree::new_child(parent, ns, c"fallback".as_ptr() as *const xmlChar)
1065 }
1066 #[allow(dead_code)]
1067 unsafe fn find_element(doc: *mut _xmlDoc, name: *const xmlChar) -> *mut _xmlNode {
1069 if doc.is_null() {
1070 return ptr::null_mut();
1071 }
1072 let mut child = unsafe { (*doc).children };
1073 while !child.is_null() {
1074 let result = unsafe { find_element_recursive(child, name) };
1075 if !result.is_null() {
1076 return result;
1077 }
1078 child = unsafe { (*child).next };
1079 }
1080 ptr::null_mut()
1081 }
1082
1083 #[allow(dead_code)]
1084 unsafe fn find_element_recursive(node: *mut _xmlNode, name: *const xmlChar) -> *mut _xmlNode {
1085 if node.is_null() {
1086 return ptr::null_mut();
1087 }
1088 let n = unsafe { &*node };
1089 if n.type_ == XML_ELEMENT_NODE as c_int
1090 && !n.name.is_null()
1091 && unsafe { xml_str_equal(n.name, name) }
1092 {
1093 return node;
1094 }
1095 let mut child = n.children;
1096 while !child.is_null() {
1097 let result = unsafe { find_element_recursive(child, name) };
1098 if !result.is_null() {
1099 return result;
1100 }
1101 child = unsafe { (*child).next };
1102 }
1103 ptr::null_mut()
1104 }
1105
1106 unsafe fn count_elements(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
1108 if doc.is_null() {
1109 return 0;
1110 }
1111 let mut count: c_int = 0;
1112 let mut child = unsafe { (*doc).children };
1113 while !child.is_null() {
1114 count += unsafe { count_elements_recursive(child, name) };
1115 child = unsafe { (*child).next };
1116 }
1117 count
1118 }
1119
1120 unsafe fn count_elements_recursive(node: *mut _xmlNode, name: *const xmlChar) -> c_int {
1121 if node.is_null() {
1122 return 0;
1123 }
1124 let mut count: c_int = 0;
1125 let n = unsafe { &*node };
1126 if n.type_ == XML_ELEMENT_NODE as c_int
1127 && !n.name.is_null()
1128 && unsafe { xml_str_equal(n.name, name) }
1129 {
1130 count += 1;
1131 }
1132 let mut child = n.children;
1133 while !child.is_null() {
1134 count += unsafe { count_elements_recursive(child, name) };
1135 child = unsafe { (*child).next };
1136 }
1137 count
1138 }
1139
1140 #[test]
1145 fn test_is_xinclude_element() {
1146 unsafe {
1147 let doc = create_simple_doc();
1148 assert!(!doc.is_null());
1149 let root = (*doc).children;
1150 assert!(!root.is_null());
1151 assert!(!is_xinclude_element(root));
1152 assert!(!is_xinclude_element(ptr::null_mut()));
1153 tree::free_doc(doc);
1154 }
1155 }
1156
1157 #[test]
1158 fn test_xinclude_namespace_detection() {
1159 unsafe {
1160 let (doc, root) = create_doc_with_xinclude_ns();
1161 let include = create_include_child(root, Some(b"test.xml"), None);
1162 assert!(!include.is_null());
1163 assert!(is_xinclude_element(include), "Should detect xi:include");
1164
1165 let regular =
1167 tree::new_child(root, ptr::null_mut(), c"regular".as_ptr() as *const xmlChar);
1168 assert!(!regular.is_null());
1169 assert!(!is_xinclude_element(regular), "Regular elem not xinclude");
1170
1171 tree::free_doc(doc);
1172 }
1173 }
1174
1175 #[test]
1176 fn test_find_fallback_child() {
1177 unsafe {
1178 let (doc, root) = create_doc_with_xinclude_ns();
1179 let include = create_include_child(root, None, None);
1180 assert!(!include.is_null());
1181 let fallback = create_fallback_child(include);
1182 assert!(!fallback.is_null());
1183
1184 let found = find_fallback_child(include);
1185 assert!(!found.is_null(), "Should find fallback child");
1186
1187 let no_fallback = find_fallback_child(root);
1188 assert!(no_fallback.is_null(), "Root should not have fallback");
1189
1190 tree::free_doc(doc);
1191 }
1192 }
1193
1194 #[test]
1195 fn test_xml_str_equal() {
1196 unsafe {
1197 assert!(xml_str_equal(
1198 c"hello".as_ptr() as *const xmlChar,
1199 c"hello".as_ptr() as *const xmlChar,
1200 ));
1201 assert!(!xml_str_equal(
1202 c"hello".as_ptr() as *const xmlChar,
1203 c"world".as_ptr() as *const xmlChar,
1204 ));
1205 assert!(!xml_str_equal(
1206 ptr::null(),
1207 c"hello".as_ptr() as *const xmlChar
1208 ));
1209 assert!(!xml_str_equal(
1210 c"hello".as_ptr() as *const xmlChar,
1211 ptr::null()
1212 ));
1213 assert!(xml_str_equal(ptr::null(), ptr::null()));
1214 }
1215 }
1216
1217 #[test]
1218 fn test_xinclude_process_null_doc() {
1219 unsafe {
1220 assert_eq!(xinclude_process(ptr::null_mut()), XINCLUDE_FAILURE);
1221 }
1222 }
1223
1224 #[test]
1225 fn test_xinclude_process_no_includes() {
1226 unsafe {
1227 let doc = create_simple_doc();
1228 assert_eq!(xinclude_process(doc), 0);
1229 tree::free_doc(doc);
1230 }
1231 }
1232
1233 #[test]
1234 fn test_xinclude_process_with_includes() {
1235 unsafe {
1236 let (doc, root) = create_doc_with_xinclude_ns();
1238 create_include_child(root, Some(b"nonexistent.xml"), None);
1239 let result = xinclude_process(doc);
1240 assert!(result >= 0, "Should handle missing files: {}", result);
1241 tree::free_doc(doc);
1242 }
1243 }
1244
1245 #[test]
1246 fn test_xinclude_fallback_content() {
1247 unsafe {
1248 let (doc, root) = create_doc_with_xinclude_ns();
1249 let include = create_include_child(root, Some(b"nonexistent.xml"), None);
1250 let fb = create_fallback_child(include);
1251 let fb_child = tree::new_child(
1253 fb,
1254 ptr::null_mut(),
1255 c"fallback-elem".as_ptr() as *const xmlChar,
1256 );
1257 assert!(!fb_child.is_null());
1258
1259 let before = count_elements(doc, c"fallback-elem".as_ptr() as *const xmlChar);
1260 assert!(before > 0, "Should have fallback-elem before processing");
1261
1262 let result = xinclude_process(doc);
1263 assert!(result >= 0, "Should handle fallback: {}", result);
1264 tree::free_doc(doc);
1265 }
1266 }
1267
1268 #[test]
1269 fn test_xinclude_circular_reference_detection() {
1270 unsafe {
1271 let (doc, root) = create_doc_with_xinclude_ns();
1272 create_include_child(root, Some(b"self-ref.xml"), None);
1273 let result = xinclude_process(doc);
1274 assert!(result >= 0, "Circular ref should not crash: {}", result);
1275 tree::free_doc(doc);
1276 }
1277 }
1278
1279 #[test]
1280 fn test_xinclude_parse_attribute_detection() {
1281 unsafe {
1282 let (doc, root) = create_doc_with_xinclude_ns();
1283 create_include_child(root, Some(b"test.xml"), Some(b"xml"));
1284 create_include_child(root, Some(b"test.txt"), Some(b"text"));
1285 create_include_child(root, Some(b"default.xml"), None);
1286
1287 let mut count = 0;
1289 let mut child = (*root).children;
1290 while !child.is_null() {
1291 if is_xinclude_element(child) {
1292 count += 1;
1293 }
1294 child = (*child).next;
1295 }
1296 assert_eq!(count, 3, "Should have 3 include elements");
1297 tree::free_doc(doc);
1298 }
1299 }
1300
1301 #[test]
1302 fn test_xinclude_process_functions() {
1303 unsafe {
1304 let doc = create_simple_doc();
1305 let r1 = xinclude_process(doc);
1306 assert!(r1 >= 0);
1307 let r2 = xinclude_process_flags(doc, 0);
1308 assert!(r2 >= 0);
1309 tree::free_doc(doc);
1310 }
1311 }
1312
1313 #[test]
1314 fn test_xinclude_process_with_empty_href() {
1315 unsafe {
1316 let (doc, root) = create_doc_with_xinclude_ns();
1317 let include = create_include_child(root, None, None);
1318 create_fallback_child(include);
1319 let result = xinclude_process(doc);
1320 assert!(result >= 0, "Empty href with fallback: {}", result);
1321 tree::free_doc(doc);
1322 }
1323 }
1324
1325 #[test]
1326 fn test_set_doc_recursive() {
1327 unsafe {
1328 let doc = tree::new_doc(ptr::null());
1329 assert!(!doc.is_null());
1330 let parent = tree::new_child(
1331 doc as *mut _xmlNode,
1332 ptr::null_mut(),
1333 c"parent".as_ptr() as *const xmlChar,
1334 );
1335 assert!(!parent.is_null());
1336 let detached = tree::new_node(ptr::null_mut(), c"detached".as_ptr() as *const xmlChar);
1337 assert!(!detached.is_null());
1338 assert!((*detached).doc.is_null());
1339 set_doc_recursive(detached, doc);
1340 assert_eq!((*detached).doc, doc);
1341 tree::free_node(detached);
1342 tree::free_doc(doc);
1343 }
1344 }
1345
1346 #[test]
1347 fn test_find_root_element() {
1348 unsafe {
1349 let doc = create_simple_doc();
1350 let root = find_root_element(doc);
1351 assert!(!root.is_null());
1352 assert_eq!((*root).type_, XML_ELEMENT_NODE as c_int);
1353 tree::free_doc(doc);
1354 }
1355 }
1356
1357 #[test]
1358 fn test_xinclude_xpointer_attribute() {
1359 unsafe {
1360 let (doc, root) = create_doc_with_xinclude_ns();
1361 let include = create_include_child(root, Some(b"test.xml"), None);
1362 let xptr_val = crate::xml::string::bytes_to_xmlstr(b"xpointer(//target)");
1363 tree::set_prop(include, ATTR_XPOINTER.as_ptr() as *const xmlChar, xptr_val);
1364 allocator::xmlFreeImpl(xptr_val as *mut c_void);
1365
1366 let xptr = tree::get_prop(include, ATTR_XPOINTER.as_ptr() as *const xmlChar);
1367 assert!(!xptr.is_null(), "Should have xpointer attribute");
1368 assert_eq!(xmlstr_to_bytes(xptr), b"xpointer(//target)");
1369 allocator::xmlFreeImpl(xptr as *mut c_void);
1370
1371 tree::free_doc(doc);
1372 }
1373 }
1374
1375 #[test]
1376 fn test_xinclude_accept_attributes() {
1377 unsafe {
1378 let (doc, root) = create_doc_with_xinclude_ns();
1379 let include = create_include_child(root, Some(b"data.xml"), None);
1380
1381 let accept_val = crate::xml::string::bytes_to_xmlstr(b"application/xml");
1382 tree::set_prop(include, ATTR_ACCEPT.as_ptr() as *const xmlChar, accept_val);
1383 allocator::xmlFreeImpl(accept_val as *mut c_void);
1384
1385 let lang_val = crate::xml::string::bytes_to_xmlstr(b"en");
1386 tree::set_prop(
1387 include,
1388 ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar,
1389 lang_val,
1390 );
1391 allocator::xmlFreeImpl(lang_val as *mut c_void);
1392
1393 let accept = tree::get_prop(include, ATTR_ACCEPT.as_ptr() as *const xmlChar);
1394 assert!(!accept.is_null());
1395 assert_eq!(xmlstr_to_bytes(accept), b"application/xml");
1396 allocator::xmlFreeImpl(accept as *mut c_void);
1397
1398 let lang = tree::get_prop(include, ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar);
1399 assert!(!lang.is_null());
1400 assert_eq!(xmlstr_to_bytes(lang), b"en");
1401 allocator::xmlFreeImpl(lang as *mut c_void);
1402
1403 tree::free_doc(doc);
1404 }
1405 }
1406
1407 #[test]
1408 fn test_xinclude_encoding_attribute() {
1409 unsafe {
1410 let (doc, root) = create_doc_with_xinclude_ns();
1411 let include = create_include_child(root, Some(b"data.txt"), Some(b"text"));
1412
1413 let enc_val = crate::xml::string::bytes_to_xmlstr(b"UTF-8");
1414 tree::set_prop(include, ATTR_ENCODING.as_ptr() as *const xmlChar, enc_val);
1415 allocator::xmlFreeImpl(enc_val as *mut c_void);
1416
1417 let encoding = tree::get_prop(include, ATTR_ENCODING.as_ptr() as *const xmlChar);
1418 assert!(!encoding.is_null());
1419 assert_eq!(xmlstr_to_bytes(encoding), b"UTF-8");
1420 allocator::xmlFreeImpl(encoding as *mut c_void);
1421
1422 tree::free_doc(doc);
1423 }
1424 }
1425
1426 #[test]
1427 fn test_xinclude_process_flags_equivalence() {
1428 unsafe {
1429 let doc = create_simple_doc();
1430 let r1 = xinclude_process(doc);
1431 let r2 = xinclude_process_flags(doc, 0);
1432 assert_eq!(r1, r2);
1433 tree::free_doc(doc);
1434 }
1435 }
1436
1437 #[test]
1438 fn test_xinclude_process_flags_noxincnode() {
1439 unsafe {
1440 let doc = create_simple_doc();
1441 let result = xinclude_process_flags(doc, XML_PARSE_NOXINCNODE);
1442 assert_eq!(result, 0);
1443 tree::free_doc(doc);
1444 }
1445 }
1446
1447 #[test]
1448 #[ignore = "pre-existing tree module cleanup bug with modified trees"]
1449 fn test_complex_nested_includes_structure() {
1450 unsafe {
1451 let doc = tree::new_doc(ptr::null());
1453 assert!(!doc.is_null());
1454 let root = tree::new_child(
1455 doc as *mut _xmlNode,
1456 ptr::null_mut(),
1457 c"root".as_ptr() as *const xmlChar,
1458 );
1459 assert!(!root.is_null());
1460 create_ns(
1461 root,
1462 c"xi".as_ptr() as *const xmlChar,
1463 XINCLUDE_NS.as_ptr() as *const xmlChar,
1464 );
1465
1466 create_include_child(root, Some(b"nonexistent1.xml"), None);
1467
1468 let inc2 = create_include_child(root, Some(b"nonexistent2.xml"), None);
1469 let fb2 = create_fallback_child(inc2);
1470 tree::new_child(
1471 fb2,
1472 ptr::null_mut(),
1473 c"fallback-content".as_ptr() as *const xmlChar,
1474 );
1475
1476 create_include_child(root, Some(b"nonexistent3.txt"), Some(b"text"));
1477
1478 let result = xinclude_process(doc);
1479 assert!(result >= 0, "Complex structure: {}", result);
1480 }
1481 }
1482
1483 #[test]
1484 fn test_xinclude_process_xml_memory_cleanup() {
1485 unsafe {
1486 let doc = create_simple_doc();
1487 assert!(xinclude_process(doc) >= 0);
1488 tree::free_doc(doc);
1489 }
1490 }
1491
1492 #[test]
1493 fn test_mark_doc_xinclude_processed() {
1494 unsafe {
1495 let doc = create_simple_doc();
1496 assert_eq!((*doc).properties & XML_DOC_XINCLUDE as c_int, 0);
1497 mark_doc_xinclude_processed(doc);
1498 assert_ne!((*doc).properties & XML_DOC_XINCLUDE as c_int, 0);
1499 tree::free_doc(doc);
1500 }
1501 }
1502
1503 #[test]
1504 fn test_xinclude_xinclude_start_end_nodes() {
1505 unsafe {
1506 let doc = create_simple_doc();
1507 let root = find_root_element(doc);
1508 assert!(!root.is_null());
1509
1510 let sentinel =
1512 tree::new_node(ptr::null_mut(), c"XIncludeStart".as_ptr() as *const xmlChar);
1513 assert!(!sentinel.is_null());
1514 (*sentinel).type_ = XML_XINCLUDE_START as c_int;
1515 (*sentinel).doc = doc;
1516 let first_child = (*root).children;
1518 if !first_child.is_null() {
1519 (*sentinel).parent = root;
1521 (*sentinel).prev = first_child;
1522 (*sentinel).next = (*first_child).next;
1523 if !(*first_child).next.is_null() {
1524 (*(*first_child).next).prev = sentinel;
1525 }
1526 (*first_child).next = sentinel;
1527 if (*root).last == first_child {
1528 (*root).last = sentinel;
1529 }
1530 }
1531
1532 let mut visited = Vec::new();
1533 let count = { process_node_tree(root, doc, &mut visited) };
1534 assert_eq!(count, 0, "Should not process sentinel nodes");
1535
1536 if !(*sentinel).prev.is_null() {
1538 (*(*sentinel).prev).next = (*sentinel).next;
1539 }
1540 if !(*sentinel).next.is_null() {
1541 (*(*sentinel).next).prev = (*sentinel).prev;
1542 }
1543 (*sentinel).prev = ptr::null_mut();
1544 (*sentinel).next = ptr::null_mut();
1545 (*sentinel).parent = ptr::null_mut();
1546
1547 tree::free_node(sentinel);
1548 tree::free_doc(doc);
1549 }
1550 }
1551}