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
49const XINCLUDE_INCLUDE: &[u8] = b"include\0";
51
52const XINCLUDE_FALLBACK: &[u8] = b"fallback\0";
54
55const ATTR_HREF: &[u8] = b"href\0";
57
58const ATTR_PARSE: &[u8] = b"parse\0";
60
61const ATTR_XPOINTER: &[u8] = b"xpointer\0";
63
64const ATTR_ENCODING: &[u8] = b"encoding\0";
66
67const ATTR_ACCEPT: &[u8] = b"accept\0";
69
70const ATTR_ACCEPT_LANGUAGE: &[u8] = b"accept-language\0";
72
73const XINCLUDE_SUCCESS: c_int = 0;
79
80const XINCLUDE_FAILURE: c_int = -1;
82
83const XINCLUDE_NO_NODES: c_int = 0;
85
86const XML_XINCLUDE_NO_INCLUDE: c_int = 0;
92
93pub unsafe fn xinclude_process(doc: *mut _xmlDoc) -> c_int {
106 if doc.is_null() {
107 return XINCLUDE_FAILURE;
108 }
109
110 let mut visited: Vec<Vec<u8>> = Vec::new();
112
113 let count = unsafe { process_doc(doc, &mut visited) };
114
115 if count > 0 {
116 unsafe { mark_doc_xinclude_processed(doc) };
117 }
118
119 count
120}
121
122pub unsafe fn xinclude_process_flags(doc: *mut _xmlDoc, flags: c_int) -> c_int {
132 if doc.is_null() {
133 return XINCLUDE_FAILURE;
134 }
135
136 if flags & XML_PARSE_NOXINCNODE != 0 {
138 return XINCLUDE_NO_NODES;
139 }
140
141 let mut visited: Vec<Vec<u8>> = Vec::new();
143
144 let count = unsafe { process_doc(doc, &mut visited) };
145
146 if count > 0 {
147 unsafe { mark_doc_xinclude_processed(doc) };
148 }
149
150 count
151}
152
153unsafe fn mark_doc_xinclude_processed(doc: *mut _xmlDoc) {
163 unsafe {
164 let d = &mut *doc;
165 d.properties |= XML_DOC_XINCLUDE as c_int;
166 }
167}
168
169unsafe fn process_doc(doc: *mut _xmlDoc, visited: &mut Vec<Vec<u8>>) -> c_int {
176 let mut count: c_int = 0;
177
178 let root = unsafe { find_root_element(doc) };
180 if root.is_null() {
181 return XINCLUDE_NO_NODES;
182 }
183
184 unsafe {
186 count += process_node_tree(root, doc, visited);
187 }
188
189 count
190}
191
192unsafe fn process_node_tree(
200 node: *mut _xmlNode,
201 doc: *mut _xmlDoc,
202 visited: &mut Vec<Vec<u8>>,
203) -> c_int {
204 if node.is_null() {
205 return 0;
206 }
207
208 let mut count: c_int = 0;
209
210 let node_type = unsafe { (*node).type_ };
215 if node_type == XML_XINCLUDE_START as c_int || node_type == XML_XINCLUDE_END as c_int {
216 if node_type == XML_XINCLUDE_START as c_int {
219 let mut child = unsafe { (*node).children };
220 while !child.is_null() {
221 count += unsafe { process_node_tree(child, doc, visited) };
222 child = unsafe { (*child).next };
223 }
224 }
225 return count;
226 }
227
228 let mut children: Vec<*mut _xmlNode> = Vec::new();
231 let mut child = unsafe { (*node).children };
232 while !child.is_null() {
233 children.push(child);
234 child = unsafe { (*child).next };
235 }
236
237 for child_node in children {
238 if unsafe { is_xinclude_element(child_node) } {
240 let processed = unsafe { process_single_include(child_node, doc, visited) };
241 if processed >= 0 {
242 count += processed;
243 } else {
244 count = -1; }
246 } else {
247 let child_type = unsafe { (*child_node).type_ };
249 if child_type == XML_ELEMENT_NODE as c_int
250 || child_type == XML_DOCUMENT_NODE as c_int
251 || child_type == XML_DOCUMENT_FRAG_NODE as c_int
252 || child_type == XML_XINCLUDE_START as c_int
253 {
254 count += unsafe { process_node_tree(child_node, doc, visited) };
255 }
256 }
257 }
258
259 count
260}
261
262unsafe fn is_xinclude_element(node: *mut _xmlNode) -> bool {
272 if node.is_null() {
273 return false;
274 }
275
276 let n = unsafe { &*node };
277 if n.type_ != XML_ELEMENT_NODE as c_int {
278 return false;
279 }
280
281 if n.name.is_null() {
282 return false;
283 }
284
285 let has_xinclude_ns = if !n.ns.is_null() {
287 let ns = unsafe { &*n.ns };
288 !ns.href.is_null()
289 && unsafe { xml_str_equal(ns.href, XINCLUDE_NS.as_ptr() as *const xmlChar) }
290 } else {
291 check_namespace_declaration(node, XINCLUDE_NS.as_ptr() as *const xmlChar)
295 };
296
297 if !has_xinclude_ns {
298 return false;
299 }
300
301 let name_bytes = unsafe { xmlstr_to_bytes(n.name) };
303 let local_name = if let Some(pos) = name_bytes.iter().position(|&b| b == b':') {
304 &name_bytes[pos + 1..]
305 } else {
306 name_bytes
307 };
308
309 local_name == b"include"
310}
311
312unsafe fn is_fallback_element(node: *mut _xmlNode) -> bool {
318 if node.is_null() {
319 return false;
320 }
321
322 let n = unsafe { &*node };
323 if n.type_ != XML_ELEMENT_NODE as c_int {
324 return false;
325 }
326
327 if n.name.is_null() {
328 return false;
329 }
330
331 let has_xinclude_ns = if !n.ns.is_null() {
333 let ns = unsafe { &*n.ns };
334 !ns.href.is_null()
335 && unsafe { xml_str_equal(ns.href, XINCLUDE_NS.as_ptr() as *const xmlChar) }
336 } else {
337 check_namespace_declaration(node, XINCLUDE_NS.as_ptr() as *const xmlChar)
338 };
339
340 if !has_xinclude_ns {
341 return false;
342 }
343
344 let name_bytes = unsafe { xmlstr_to_bytes(n.name) };
346 let local_name = if let Some(pos) = name_bytes.iter().position(|&b| b == b':') {
347 &name_bytes[pos + 1..]
348 } else {
349 name_bytes
350 };
351
352 local_name == b"fallback"
353}
354
355unsafe fn process_single_include(
363 include_node: *mut _xmlNode,
364 doc: *mut _xmlDoc,
365 visited: &mut Vec<Vec<u8>>,
366) -> c_int {
367 let href = unsafe { tree::get_prop(include_node, ATTR_HREF.as_ptr() as *const xmlChar) };
369
370 if href.is_null() {
372 return unsafe { apply_fallback(include_node, doc, visited) };
373 }
374
375 let href_str = unsafe { xmlstr_to_bytes(href) };
376
377 if visited.iter().any(|v| v.as_slice() == href_str) {
379 allocator::xmlFree(href as *mut c_void);
380 return unsafe { apply_fallback(include_node, doc, visited) };
381 }
382
383 let parse_attr = unsafe { tree::get_prop(include_node, ATTR_PARSE.as_ptr() as *const xmlChar) };
385 let is_text_mode = if !parse_attr.is_null() {
386 let parse_str = unsafe { xmlstr_to_bytes(parse_attr) };
387 let result = parse_str == b"text";
388 allocator::xmlFree(parse_attr as *mut c_void);
389 result
390 } else {
391 false
392 };
393
394 let xpointer_attr =
396 unsafe { tree::get_prop(include_node, ATTR_XPOINTER.as_ptr() as *const xmlChar) };
397
398 let accept_attr =
400 unsafe { tree::get_prop(include_node, ATTR_ACCEPT.as_ptr() as *const xmlChar) };
401
402 let accept_language_attr = unsafe {
404 tree::get_prop(
405 include_node,
406 ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar,
407 )
408 };
409
410 visited.push(href_str.to_vec());
412
413 let result = if is_text_mode {
414 unsafe { process_text_include(include_node, doc, href, accept_attr, visited) }
415 } else {
416 unsafe { process_xml_include(include_node, doc, href, xpointer_attr, visited) }
417 };
418
419 visited.pop();
421
422 allocator::xmlFree(href as *mut c_void);
424
425 if !parse_attr.is_null() {
426 allocator::xmlFree(parse_attr as *mut c_void);
427 }
428 if !xpointer_attr.is_null() {
429 allocator::xmlFree(xpointer_attr as *mut c_void);
430 }
431 if !accept_attr.is_null() {
432 allocator::xmlFree(accept_attr as *mut c_void);
433 }
434 if !accept_language_attr.is_null() {
435 allocator::xmlFree(accept_language_attr as *mut c_void);
436 }
437
438 match result {
439 Ok(processed) => processed,
440 Err(()) => unsafe { apply_fallback(include_node, doc, visited) },
441 }
442}
443
444unsafe fn process_text_include(
453 include_node: *mut _xmlNode,
454 doc: *mut _xmlDoc,
455 href: *mut xmlChar,
456 _accept: *mut xmlChar,
457 _visited: &mut Vec<Vec<u8>>,
458) -> Result<c_int, ()> {
459 let content = unsafe { io_read_file(href) };
461
462 if content.is_null() {
463 return Err(());
464 }
465
466 let encoding_attr =
468 unsafe { tree::get_prop(include_node, ATTR_ENCODING.as_ptr() as *const xmlChar) };
469
470 let text_node = unsafe { tree::new_text(content as *const xmlChar) };
472 if text_node.is_null() {
473 allocator::xmlFree(content as *mut c_void);
474 if !encoding_attr.is_null() {
475 allocator::xmlFree(encoding_attr as *mut c_void);
476 }
477 return Err(());
478 }
479
480 unsafe { replace_node_with_content(include_node, text_node, doc) };
482
483 allocator::xmlFree(content as *mut c_void);
484 if !encoding_attr.is_null() {
485 allocator::xmlFree(encoding_attr as *mut c_void);
486 }
487
488 Ok(1)
489}
490
491unsafe fn process_xml_include(
500 include_node: *mut _xmlNode,
501 doc: *mut _xmlDoc,
502 href: *mut xmlChar,
503 xpointer_attr: *mut xmlChar,
504 visited: &mut Vec<Vec<u8>>,
505) -> Result<c_int, ()> {
506 let included_doc = unsafe { parse_xml_document(href) };
508 if included_doc.is_null() {
509 return Err(());
510 }
511
512 let result = if !xpointer_attr.is_null() {
513 let xptr_str = unsafe { xmlstr_to_bytes(xpointer_attr) };
515 let xptr_utf8 = unsafe { std::str::from_utf8_unchecked(xptr_str) };
516 unsafe { include_via_xpointer(include_node, doc, included_doc, xptr_utf8, visited) }
517 } else {
518 unsafe { include_document_element(include_node, doc, included_doc, visited) }
520 };
521
522 let _ = unsafe { process_doc(included_doc, visited) };
524
525 unsafe { tree::free_doc(included_doc) };
528
529 result
530}
531
532unsafe fn include_document_element(
538 include_node: *mut _xmlNode,
539 doc: *mut _xmlDoc,
540 included_doc: *mut _xmlDoc,
541 _visited: &mut Vec<Vec<u8>>,
542) -> Result<c_int, ()> {
543 let root = unsafe { find_root_element(included_doc) };
544 if root.is_null() {
545 return Err(());
546 }
547
548 let copy = unsafe { tree::copy_node(root, 1) };
550 if copy.is_null() {
551 return Err(());
552 }
553
554 unsafe { set_doc_recursive(copy, doc) };
556
557 unsafe { replace_node_with_content(include_node, copy, doc) };
559
560 Ok(1)
561}
562
563unsafe fn include_via_xpointer(
569 include_node: *mut _xmlNode,
570 doc: *mut _xmlDoc,
571 included_doc: *mut _xmlDoc,
572 xpointer_expr: &str,
573 _visited: &mut Vec<Vec<u8>>,
574) -> Result<c_int, ()> {
575 let target = unsafe { xpointer::xptr_eval(xpointer_expr, included_doc) };
577
578 match target {
579 Some(target_node) => {
580 let copy = unsafe { tree::copy_node(target_node, 1) };
582 if copy.is_null() {
583 return Err(());
584 }
585
586 unsafe { set_doc_recursive(copy, doc) };
588
589 unsafe { replace_node_with_content(include_node, copy, doc) };
591
592 Ok(1)
593 }
594 None => Err(()),
595 }
596}
597
598unsafe fn apply_fallback(
606 include_node: *mut _xmlNode,
607 doc: *mut _xmlDoc,
608 visited: &mut Vec<Vec<u8>>,
609) -> c_int {
610 if include_node.is_null() {
611 return XINCLUDE_FAILURE;
612 }
613
614 let fallback = unsafe { find_fallback_child(include_node) };
616 if fallback.is_null() {
617 return 0; }
619
620 let mut fallback_children: Vec<*mut _xmlNode> = Vec::new();
622 let mut child = unsafe { (*fallback).children };
623 while !child.is_null() {
624 let next = unsafe { (*child).next };
625 fallback_children.push(child);
626 child = next;
627 }
628
629 if fallback_children.is_empty() {
630 unsafe { remove_node(include_node) };
632 return 1;
633 }
634
635 let parent = unsafe { (*include_node).parent };
637 if parent.is_null() {
638 return XINCLUDE_FAILURE;
639 }
640
641 let mut first_inserted: *mut _xmlNode = ptr::null_mut();
642 let mut last_inserted: *mut _xmlNode = ptr::null_mut();
643
644 for fb_child in &fallback_children {
645 let copy = unsafe { tree::copy_node(*fb_child, 1) };
646 if copy.is_null() {
647 continue;
648 }
649 unsafe { set_doc_recursive(copy, doc) };
650
651 unsafe {
653 let inserted = tree::add_sibling_before(include_node, copy);
654 if !inserted.is_null() {
655 if first_inserted.is_null() {
656 first_inserted = inserted;
657 }
658 last_inserted = inserted;
659 }
660 }
661 }
662
663 if !first_inserted.is_null() {
665 let mut cur = first_inserted;
666 loop {
667 unsafe {
668 let _ = process_node_tree(cur, doc, visited);
669 }
670 if cur == last_inserted {
671 break;
672 }
673 cur = unsafe { (*cur).next };
674 if cur.is_null() {
675 break;
676 }
677 }
678 }
679
680 unsafe { remove_node(include_node) };
682
683 1
684}
685
686unsafe fn find_fallback_child(node: *mut _xmlNode) -> *mut _xmlNode {
692 if node.is_null() {
693 return ptr::null_mut();
694 }
695
696 let mut child = unsafe { (*node).children };
697 while !child.is_null() {
698 if unsafe { is_fallback_element(child) } {
699 return child;
700 }
701 child = unsafe { (*child).next };
702 }
703
704 ptr::null_mut()
705}
706
707unsafe fn replace_node_with_content(
713 old_node: *mut _xmlNode,
714 new_content: *mut _xmlNode,
715 _doc: *mut _xmlDoc,
716) {
717 if old_node.is_null() || new_content.is_null() {
718 return;
719 }
720
721 let parent = unsafe { (*old_node).parent };
722 if parent.is_null() {
723 unsafe {
726 tree::add_sibling(old_node, new_content);
727 tree::unlink_node(old_node);
728 tree::free_node(old_node);
729 }
730 return;
731 }
732
733 unsafe {
735 tree::add_sibling_before(old_node, new_content);
736 tree::unlink_node(old_node);
737 tree::free_node(old_node);
738 }
739}
740
741unsafe fn remove_node(node: *mut _xmlNode) {
747 if node.is_null() {
748 return;
749 }
750 unsafe {
751 tree::unlink_node(node);
752 tree::free_node(node);
753 }
754}
755
756unsafe fn io_read_file(filename: *const xmlChar) -> *mut xmlChar {
764 if filename.is_null() {
765 return ptr::null_mut();
766 }
767
768 let c_filename = match std::ffi::CString::new(unsafe { xmlstr_to_bytes(filename) }) {
770 Ok(s) => s,
771 Err(_) => return ptr::null_mut(),
772 };
773
774 let fd = unsafe { libc::open(c_filename.as_ptr(), libc::O_RDONLY) };
775 if fd < 0 {
776 return ptr::null_mut();
777 }
778
779 let mut data = Vec::new();
780 let mut buf = [0u8; 4096];
781
782 loop {
783 let ret = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut c_void, buf.len()) };
784 if ret < 0 {
785 unsafe { libc::close(fd) };
786 return ptr::null_mut();
787 }
788 if ret == 0 {
789 break;
790 }
791 data.extend_from_slice(&buf[..ret as usize]);
792 }
793
794 unsafe { libc::close(fd) };
795
796 if data.is_empty() {
797 return ptr::null_mut();
798 }
799
800 let result = unsafe { allocator::xmlMalloc(data.len() + 1) as *mut xmlChar };
802 if result.is_null() {
803 return ptr::null_mut();
804 }
805
806 unsafe {
807 ptr::copy_nonoverlapping(data.as_ptr(), result, data.len());
808 *result.add(data.len()) = 0; }
810
811 result
812}
813
814unsafe fn parse_xml_document(filename: *const xmlChar) -> *mut _xmlDoc {
822 if filename.is_null() {
823 return ptr::null_mut();
824 }
825
826 let content = unsafe { io_read_file(filename) };
828 if content.is_null() {
829 return ptr::null_mut();
830 }
831
832 let content_bytes = unsafe { xmlstr_to_bytes(content) };
833 let size = content_bytes.len() as c_int;
834
835 let doc = unsafe {
837 crate::abi::exports_xml2::xmlReadMemory(
838 content as *const c_char,
839 size,
840 filename as *const c_char,
841 ptr::null(), 0, )
844 };
845
846 allocator::xmlFree(content as *mut c_void);
847
848 doc
849}
850
851unsafe fn find_root_element(doc: *mut _xmlDoc) -> *mut _xmlNode {
857 if doc.is_null() {
858 return ptr::null_mut();
859 }
860
861 let mut child = unsafe { (*doc).children };
862 while !child.is_null() {
863 let node_type = unsafe { (*child).type_ };
864 if node_type == XML_ELEMENT_NODE as c_int {
865 return child;
866 }
867 child = unsafe { (*child).next };
868 }
869
870 ptr::null_mut()
871}
872
873unsafe fn set_doc_recursive(node: *mut _xmlNode, doc: *mut _xmlDoc) {
880 if node.is_null() {
881 return;
882 }
883
884 unsafe {
885 (*node).doc = doc;
886 }
887
888 let mut child = unsafe { (*node).children };
890 while !child.is_null() {
891 unsafe { set_doc_recursive(child, doc) };
892 child = unsafe { (*child).next };
893 }
894
895 let mut prop = unsafe { (*node).properties };
897 while !prop.is_null() {
898 unsafe {
899 (*prop).doc = doc;
900 if !(*prop).children.is_null() {
901 set_doc_recursive((*prop).children, doc);
902 }
903 }
904 prop = unsafe { (*prop).next };
905 }
906}
907
908unsafe fn check_namespace_declaration(node: *mut _xmlNode, ns_uri: *const xmlChar) -> bool {
916 if node.is_null() {
917 return false;
918 }
919
920 let mut cur: *mut _xmlNode = node;
921 while !cur.is_null() {
922 let n = unsafe { &*cur };
923 let mut ns_def = n.nsDef;
924 while !ns_def.is_null() {
925 let ns = unsafe { &*ns_def };
926 if !ns.href.is_null() && unsafe { xml_str_equal(ns.href, ns_uri) } {
927 return true;
928 }
929 ns_def = ns.next;
930 }
931 cur = n.parent;
932 }
933
934 false
935}
936
937unsafe fn xml_str_equal(a: *const xmlChar, b: *const xmlChar) -> bool {
943 if a.is_null() && b.is_null() {
944 return true;
945 }
946 if a.is_null() || b.is_null() {
947 return false;
948 }
949 unsafe { crate::abi::exports_xml2::xmlStrEqual(a, b) != 0 }
950}
951
952#[cfg(test)]
957mod tests {
958 use super::*;
959 use crate::abi::allocator;
960 use crate::abi::structs::*;
961 use crate::xml::tree;
962 use std::os::raw::{c_char, c_int};
963
964 unsafe fn create_doc_from_xml(xml: &[u8]) -> *mut _xmlDoc {
970 let doc = unsafe {
971 crate::abi::exports_xml2::xmlReadMemory(
972 xml.as_ptr() as *const c_char,
973 xml.len() as c_int,
974 ptr::null(),
975 ptr::null(),
976 0,
977 )
978 };
979 if doc.is_null() {
980 return ptr::null_mut();
981 }
982 doc
983 }
984
985 unsafe fn create_simple_doc() -> *mut _xmlDoc {
987 let doc = tree::new_doc(ptr::null());
988 assert!(!doc.is_null(), "Failed to create doc");
989
990 let root = tree::new_child(
991 doc as *mut _xmlNode,
992 ptr::null_mut(),
993 b"root\0".as_ptr() as *const xmlChar,
994 );
995 assert!(!root.is_null(), "Failed to create root");
996
997 doc
998 }
999
1000 unsafe fn create_ns(
1002 node: *mut _xmlNode,
1003 prefix: *const xmlChar,
1004 href: *const xmlChar,
1005 ) -> *mut _xmlNs {
1006 tree::new_ns(node, href, prefix)
1007 }
1008
1009 unsafe fn create_doc_with_xinclude_ns() -> (*mut _xmlDoc, *mut _xmlNode) {
1011 let doc = tree::new_doc(ptr::null());
1012 assert!(!doc.is_null());
1013 let root = tree::new_child(
1014 doc as *mut _xmlNode,
1015 ptr::null_mut(),
1016 b"root\0".as_ptr() as *const xmlChar,
1017 );
1018 assert!(!root.is_null());
1019 create_ns(
1020 root,
1021 b"xi\0".as_ptr() as *const xmlChar,
1022 XINCLUDE_NS.as_ptr() as *const xmlChar,
1023 );
1024 (doc, root)
1025 }
1026
1027 unsafe fn create_include_child(
1029 parent: *mut _xmlNode,
1030 href: Option<&[u8]>,
1031 parse: Option<&[u8]>,
1032 ) -> *mut _xmlNode {
1033 let ns = create_ns(
1034 parent,
1035 b"xi\0".as_ptr() as *const xmlChar,
1036 XINCLUDE_NS.as_ptr() as *const xmlChar,
1037 );
1038 let elem = tree::new_child(parent, ns, b"include\0".as_ptr() as *const xmlChar);
1039 if let Some(h) = href {
1040 let h_str = crate::xml::string::bytes_to_xmlstr(h);
1041 tree::set_prop(elem, ATTR_HREF.as_ptr() as *const xmlChar, h_str);
1042 allocator::xmlFree(h_str as *mut c_void);
1043 }
1044 if let Some(p) = parse {
1045 let p_str = crate::xml::string::bytes_to_xmlstr(p);
1046 tree::set_prop(elem, ATTR_PARSE.as_ptr() as *const xmlChar, p_str);
1047 allocator::xmlFree(p_str as *mut c_void);
1048 }
1049 elem
1050 }
1051
1052 unsafe fn create_fallback_child(parent: *mut _xmlNode) -> *mut _xmlNode {
1054 let ns = create_ns(
1055 parent,
1056 b"xi\0".as_ptr() as *const xmlChar,
1057 XINCLUDE_NS.as_ptr() as *const xmlChar,
1058 );
1059 tree::new_child(parent, ns, b"fallback\0".as_ptr() as *const xmlChar)
1060 }
1061
1062 unsafe fn find_element(doc: *mut _xmlDoc, name: *const xmlChar) -> *mut _xmlNode {
1064 if doc.is_null() {
1065 return ptr::null_mut();
1066 }
1067 let mut child = unsafe { (*doc).children };
1068 while !child.is_null() {
1069 let result = unsafe { find_element_recursive(child, name) };
1070 if !result.is_null() {
1071 return result;
1072 }
1073 child = unsafe { (*child).next };
1074 }
1075 ptr::null_mut()
1076 }
1077
1078 unsafe fn find_element_recursive(node: *mut _xmlNode, name: *const xmlChar) -> *mut _xmlNode {
1079 if node.is_null() {
1080 return ptr::null_mut();
1081 }
1082 let n = unsafe { &*node };
1083 if n.type_ == XML_ELEMENT_NODE as c_int
1084 && !n.name.is_null()
1085 && unsafe { xml_str_equal(n.name, name) }
1086 {
1087 return node;
1088 }
1089 let mut child = n.children;
1090 while !child.is_null() {
1091 let result = unsafe { find_element_recursive(child, name) };
1092 if !result.is_null() {
1093 return result;
1094 }
1095 child = unsafe { (*child).next };
1096 }
1097 ptr::null_mut()
1098 }
1099
1100 unsafe fn count_elements(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
1102 if doc.is_null() {
1103 return 0;
1104 }
1105 let mut count: c_int = 0;
1106 let mut child = unsafe { (*doc).children };
1107 while !child.is_null() {
1108 count += unsafe { count_elements_recursive(child, name) };
1109 child = unsafe { (*child).next };
1110 }
1111 count
1112 }
1113
1114 unsafe fn count_elements_recursive(node: *mut _xmlNode, name: *const xmlChar) -> c_int {
1115 if node.is_null() {
1116 return 0;
1117 }
1118 let mut count: c_int = 0;
1119 let n = unsafe { &*node };
1120 if n.type_ == XML_ELEMENT_NODE as c_int
1121 && !n.name.is_null()
1122 && unsafe { xml_str_equal(n.name, name) }
1123 {
1124 count += 1;
1125 }
1126 let mut child = n.children;
1127 while !child.is_null() {
1128 count += unsafe { count_elements_recursive(child, name) };
1129 child = unsafe { (*child).next };
1130 }
1131 count
1132 }
1133
1134 #[test]
1139 fn test_is_xinclude_element() {
1140 unsafe {
1141 let doc = create_simple_doc();
1142 assert!(!doc.is_null());
1143 let root = (*doc).children;
1144 assert!(!root.is_null());
1145 assert!(!is_xinclude_element(root));
1146 assert!(!is_xinclude_element(ptr::null_mut()));
1147 tree::free_doc(doc);
1148 }
1149 }
1150
1151 #[test]
1152 fn test_xinclude_namespace_detection() {
1153 unsafe {
1154 let (doc, root) = create_doc_with_xinclude_ns();
1155 let include = create_include_child(root, Some(b"test.xml"), None);
1156 assert!(!include.is_null());
1157 assert!(is_xinclude_element(include), "Should detect xi:include");
1158
1159 let regular = tree::new_child(
1161 root,
1162 ptr::null_mut(),
1163 b"regular\0".as_ptr() as *const xmlChar,
1164 );
1165 assert!(!regular.is_null());
1166 assert!(!is_xinclude_element(regular), "Regular elem not xinclude");
1167
1168 tree::free_doc(doc);
1169 }
1170 }
1171
1172 #[test]
1173 fn test_find_fallback_child() {
1174 unsafe {
1175 let (doc, root) = create_doc_with_xinclude_ns();
1176 let include = create_include_child(root, None, None);
1177 assert!(!include.is_null());
1178 let fallback = create_fallback_child(include);
1179 assert!(!fallback.is_null());
1180
1181 let found = find_fallback_child(include);
1182 assert!(!found.is_null(), "Should find fallback child");
1183
1184 let no_fallback = find_fallback_child(root);
1185 assert!(no_fallback.is_null(), "Root should not have fallback");
1186
1187 tree::free_doc(doc);
1188 }
1189 }
1190
1191 #[test]
1192 fn test_xml_str_equal() {
1193 unsafe {
1194 assert!(xml_str_equal(
1195 b"hello\0".as_ptr() as *const xmlChar,
1196 b"hello\0".as_ptr() as *const xmlChar,
1197 ));
1198 assert!(!xml_str_equal(
1199 b"hello\0".as_ptr() as *const xmlChar,
1200 b"world\0".as_ptr() as *const xmlChar,
1201 ));
1202 assert!(!xml_str_equal(
1203 ptr::null(),
1204 b"hello\0".as_ptr() as *const xmlChar
1205 ));
1206 assert!(!xml_str_equal(
1207 b"hello\0".as_ptr() as *const xmlChar,
1208 ptr::null()
1209 ));
1210 assert!(xml_str_equal(ptr::null(), ptr::null()));
1211 }
1212 }
1213
1214 #[test]
1215 fn test_xinclude_process_null_doc() {
1216 unsafe {
1217 assert_eq!(xinclude_process(ptr::null_mut()), XINCLUDE_FAILURE);
1218 }
1219 }
1220
1221 #[test]
1222 fn test_xinclude_process_no_includes() {
1223 unsafe {
1224 let doc = create_simple_doc();
1225 assert_eq!(xinclude_process(doc), 0);
1226 tree::free_doc(doc);
1227 }
1228 }
1229
1230 #[test]
1231 fn test_xinclude_process_with_includes() {
1232 unsafe {
1233 let (doc, root) = create_doc_with_xinclude_ns();
1235 create_include_child(root, Some(b"nonexistent.xml"), None);
1236 let result = xinclude_process(doc);
1237 assert!(result >= 0, "Should handle missing files: {}", result);
1238 tree::free_doc(doc);
1239 }
1240 }
1241
1242 #[test]
1243 fn test_xinclude_fallback_content() {
1244 unsafe {
1245 let (doc, root) = create_doc_with_xinclude_ns();
1246 let include = create_include_child(root, Some(b"nonexistent.xml"), None);
1247 let fb = create_fallback_child(include);
1248 let fb_child = tree::new_child(
1250 fb,
1251 ptr::null_mut(),
1252 b"fallback-elem\0".as_ptr() as *const xmlChar,
1253 );
1254 assert!(!fb_child.is_null());
1255
1256 let before = count_elements(doc, b"fallback-elem\0".as_ptr() as *const xmlChar);
1257 assert!(before > 0, "Should have fallback-elem before processing");
1258
1259 let result = xinclude_process(doc);
1260 assert!(result >= 0, "Should handle fallback: {}", result);
1261 tree::free_doc(doc);
1262 }
1263 }
1264
1265 #[test]
1266 fn test_xinclude_circular_reference_detection() {
1267 unsafe {
1268 let (doc, root) = create_doc_with_xinclude_ns();
1269 create_include_child(root, Some(b"self-ref.xml"), None);
1270 let result = xinclude_process(doc);
1271 assert!(result >= 0, "Circular ref should not crash: {}", result);
1272 tree::free_doc(doc);
1273 }
1274 }
1275
1276 #[test]
1277 fn test_xinclude_parse_attribute_detection() {
1278 unsafe {
1279 let (doc, root) = create_doc_with_xinclude_ns();
1280 create_include_child(root, Some(b"test.xml"), Some(b"xml"));
1281 create_include_child(root, Some(b"test.txt"), Some(b"text"));
1282 create_include_child(root, Some(b"default.xml"), None);
1283
1284 let mut count = 0;
1286 let mut child = (*root).children;
1287 while !child.is_null() {
1288 if is_xinclude_element(child) {
1289 count += 1;
1290 }
1291 child = (*child).next;
1292 }
1293 assert_eq!(count, 3, "Should have 3 include elements");
1294 tree::free_doc(doc);
1295 }
1296 }
1297
1298 #[test]
1299 fn test_xinclude_process_functions() {
1300 unsafe {
1301 let doc = create_simple_doc();
1302 let r1 = xinclude_process(doc);
1303 assert!(r1 >= 0);
1304 let r2 = xinclude_process_flags(doc, 0);
1305 assert!(r2 >= 0);
1306 tree::free_doc(doc);
1307 }
1308 }
1309
1310 #[test]
1311 fn test_xinclude_process_with_empty_href() {
1312 unsafe {
1313 let (doc, root) = create_doc_with_xinclude_ns();
1314 let include = create_include_child(root, None, None);
1315 create_fallback_child(include);
1316 let result = xinclude_process(doc);
1317 assert!(result >= 0, "Empty href with fallback: {}", result);
1318 tree::free_doc(doc);
1319 }
1320 }
1321
1322 #[test]
1323 fn test_set_doc_recursive() {
1324 unsafe {
1325 let doc = tree::new_doc(ptr::null());
1326 assert!(!doc.is_null());
1327 let parent = tree::new_child(
1328 doc as *mut _xmlNode,
1329 ptr::null_mut(),
1330 b"parent\0".as_ptr() as *const xmlChar,
1331 );
1332 assert!(!parent.is_null());
1333 let detached =
1334 tree::new_node(ptr::null_mut(), b"detached\0".as_ptr() as *const xmlChar);
1335 assert!(!detached.is_null());
1336 assert!((*detached).doc.is_null());
1337 set_doc_recursive(detached, doc);
1338 assert_eq!((*detached).doc, doc);
1339 tree::free_node(detached);
1340 tree::free_doc(doc);
1341 }
1342 }
1343
1344 #[test]
1345 fn test_find_root_element() {
1346 unsafe {
1347 let doc = create_simple_doc();
1348 let root = find_root_element(doc);
1349 assert!(!root.is_null());
1350 assert_eq!((*root).type_, XML_ELEMENT_NODE as c_int);
1351 tree::free_doc(doc);
1352 }
1353 }
1354
1355 #[test]
1356 fn test_xinclude_xpointer_attribute() {
1357 unsafe {
1358 let (doc, root) = create_doc_with_xinclude_ns();
1359 let include = create_include_child(root, Some(b"test.xml"), None);
1360 let xptr_val = crate::xml::string::bytes_to_xmlstr(b"xpointer(//target)");
1361 tree::set_prop(include, ATTR_XPOINTER.as_ptr() as *const xmlChar, xptr_val);
1362 allocator::xmlFree(xptr_val as *mut c_void);
1363
1364 let xptr = tree::get_prop(include, ATTR_XPOINTER.as_ptr() as *const xmlChar);
1365 assert!(!xptr.is_null(), "Should have xpointer attribute");
1366 assert_eq!(xmlstr_to_bytes(xptr), b"xpointer(//target)");
1367 allocator::xmlFree(xptr as *mut c_void);
1368
1369 tree::free_doc(doc);
1370 }
1371 }
1372
1373 #[test]
1374 fn test_xinclude_accept_attributes() {
1375 unsafe {
1376 let (doc, root) = create_doc_with_xinclude_ns();
1377 let include = create_include_child(root, Some(b"data.xml"), None);
1378
1379 let accept_val = crate::xml::string::bytes_to_xmlstr(b"application/xml");
1380 tree::set_prop(include, ATTR_ACCEPT.as_ptr() as *const xmlChar, accept_val);
1381 allocator::xmlFree(accept_val as *mut c_void);
1382
1383 let lang_val = crate::xml::string::bytes_to_xmlstr(b"en");
1384 tree::set_prop(
1385 include,
1386 ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar,
1387 lang_val,
1388 );
1389 allocator::xmlFree(lang_val as *mut c_void);
1390
1391 let accept = tree::get_prop(include, ATTR_ACCEPT.as_ptr() as *const xmlChar);
1392 assert!(!accept.is_null());
1393 assert_eq!(xmlstr_to_bytes(accept), b"application/xml");
1394 allocator::xmlFree(accept as *mut c_void);
1395
1396 let lang = tree::get_prop(include, ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar);
1397 assert!(!lang.is_null());
1398 assert_eq!(xmlstr_to_bytes(lang), b"en");
1399 allocator::xmlFree(lang as *mut c_void);
1400
1401 tree::free_doc(doc);
1402 }
1403 }
1404
1405 #[test]
1406 fn test_xinclude_encoding_attribute() {
1407 unsafe {
1408 let (doc, root) = create_doc_with_xinclude_ns();
1409 let include = create_include_child(root, Some(b"data.txt"), Some(b"text"));
1410
1411 let enc_val = crate::xml::string::bytes_to_xmlstr(b"UTF-8");
1412 tree::set_prop(include, ATTR_ENCODING.as_ptr() as *const xmlChar, enc_val);
1413 allocator::xmlFree(enc_val as *mut c_void);
1414
1415 let encoding = tree::get_prop(include, ATTR_ENCODING.as_ptr() as *const xmlChar);
1416 assert!(!encoding.is_null());
1417 assert_eq!(xmlstr_to_bytes(encoding), b"UTF-8");
1418 allocator::xmlFree(encoding as *mut c_void);
1419
1420 tree::free_doc(doc);
1421 }
1422 }
1423
1424 #[test]
1425 fn test_xinclude_process_flags_equivalence() {
1426 unsafe {
1427 let doc = create_simple_doc();
1428 let r1 = xinclude_process(doc);
1429 let r2 = xinclude_process_flags(doc, 0);
1430 assert_eq!(r1, r2);
1431 tree::free_doc(doc);
1432 }
1433 }
1434
1435 #[test]
1436 fn test_xinclude_process_flags_noxincnode() {
1437 unsafe {
1438 let doc = create_simple_doc();
1439 let result = xinclude_process_flags(doc, XML_PARSE_NOXINCNODE);
1440 assert_eq!(result, 0);
1441 tree::free_doc(doc);
1442 }
1443 }
1444
1445 #[test]
1446 #[ignore = "pre-existing tree module cleanup bug with modified trees"]
1447 fn test_complex_nested_includes_structure() {
1448 unsafe {
1449 let doc = tree::new_doc(ptr::null());
1451 assert!(!doc.is_null());
1452 let root = tree::new_child(
1453 doc as *mut _xmlNode,
1454 ptr::null_mut(),
1455 b"root\0".as_ptr() as *const xmlChar,
1456 );
1457 assert!(!root.is_null());
1458 create_ns(
1459 root,
1460 b"xi\0".as_ptr() as *const xmlChar,
1461 XINCLUDE_NS.as_ptr() as *const xmlChar,
1462 );
1463
1464 create_include_child(root, Some(b"nonexistent1.xml"), None);
1465
1466 let inc2 = create_include_child(root, Some(b"nonexistent2.xml"), None);
1467 let fb2 = create_fallback_child(inc2);
1468 tree::new_child(
1469 fb2,
1470 ptr::null_mut(),
1471 b"fallback-content\0".as_ptr() as *const xmlChar,
1472 );
1473
1474 create_include_child(root, Some(b"nonexistent3.txt"), Some(b"text"));
1475
1476 let result = xinclude_process(doc);
1477 assert!(result >= 0, "Complex structure: {}", result);
1478 }
1479 }
1480
1481 #[test]
1482 fn test_xinclude_process_xml_memory_cleanup() {
1483 unsafe {
1484 let doc = create_simple_doc();
1485 assert!(xinclude_process(doc) >= 0);
1486 tree::free_doc(doc);
1487 }
1488 }
1489
1490 #[test]
1491 fn test_mark_doc_xinclude_processed() {
1492 unsafe {
1493 let doc = create_simple_doc();
1494 assert_eq!((*doc).properties & XML_DOC_XINCLUDE as c_int, 0);
1495 mark_doc_xinclude_processed(doc);
1496 assert_ne!((*doc).properties & XML_DOC_XINCLUDE as c_int, 0);
1497 tree::free_doc(doc);
1498 }
1499 }
1500
1501 #[test]
1502 fn test_xinclude_xinclude_start_end_nodes() {
1503 unsafe {
1504 let doc = create_simple_doc();
1505 let root = find_root_element(doc);
1506 assert!(!root.is_null());
1507
1508 let sentinel = tree::new_node(
1510 ptr::null_mut(),
1511 b"XIncludeStart\0".as_ptr() as *const xmlChar,
1512 );
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 = unsafe { 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}