1use core::ffi::c_void;
82use core::ptr;
83use std::os::raw::{c_char, c_int};
84
85use crate::abi::allocator;
86use crate::abi::structs::*;
87use crate::abi::types::xmlDocProperties::XML_DOC_XINCLUDE;
88use crate::abi::types::xmlElementType::*;
89use crate::abi::types::*;
90use crate::xml::string::*;
91use crate::xml::tree;
92use crate::xml::xpointer;
93
94const XINCLUDE_NS: &[u8] = b"http://www.w3.org/2001/XInclude\0";
100
101#[allow(dead_code)]
103const XINCLUDE_INCLUDE: &[u8] = b"include\0";
104
105#[allow(dead_code)]
107const XINCLUDE_FALLBACK: &[u8] = b"fallback\0";
108
109const ATTR_HREF: &[u8] = b"href\0";
111
112const ATTR_PARSE: &[u8] = b"parse\0";
114
115const ATTR_XPOINTER: &[u8] = b"xpointer\0";
117
118const ATTR_ENCODING: &[u8] = b"encoding\0";
120
121const ATTR_ACCEPT: &[u8] = b"accept\0";
123
124const ATTR_ACCEPT_LANGUAGE: &[u8] = b"accept-language\0";
126
127#[allow(dead_code)]
133const XINCLUDE_SUCCESS: c_int = 0;
134
135const XINCLUDE_FAILURE: c_int = -1;
137
138const XINCLUDE_NO_NODES: c_int = 0;
140
141#[allow(dead_code)]
147const XML_XINCLUDE_NO_INCLUDE: c_int = 0;
148
149pub unsafe fn xinclude_process(doc: *mut _xmlDoc) -> c_int {
162 if doc.is_null() {
163 return XINCLUDE_FAILURE;
164 }
165
166 let mut visited: Vec<Vec<u8>> = Vec::new();
168
169 let count = unsafe { process_doc(doc, &mut visited) };
170
171 if count > 0 {
172 unsafe { mark_doc_xinclude_processed(doc) };
173 }
174
175 count
176}
177
178pub unsafe fn xinclude_process_flags(doc: *mut _xmlDoc, flags: c_int) -> c_int {
188 if doc.is_null() {
189 return XINCLUDE_FAILURE;
190 }
191
192 if flags & XML_PARSE_NOXINCNODE != 0 {
194 return XINCLUDE_NO_NODES;
195 }
196
197 let mut visited: Vec<Vec<u8>> = Vec::new();
199
200 let count = unsafe { process_doc(doc, &mut visited) };
201
202 if count > 0 {
203 unsafe { mark_doc_xinclude_processed(doc) };
204 }
205
206 count
207}
208
209unsafe fn mark_doc_xinclude_processed(doc: *mut _xmlDoc) {
219 unsafe {
220 let d = &mut *doc;
221 d.properties |= XML_DOC_XINCLUDE as c_int;
222 }
223}
224
225unsafe fn process_doc(doc: *mut _xmlDoc, visited: &mut Vec<Vec<u8>>) -> c_int {
232 let mut count: c_int = 0;
233
234 let root = unsafe { find_root_element(doc) };
236 if root.is_null() {
237 return XINCLUDE_NO_NODES;
238 }
239
240 unsafe {
242 count += process_node_tree(root, doc, visited);
243 }
244
245 count
246}
247
248unsafe fn process_node_tree(
256 node: *mut _xmlNode,
257 doc: *mut _xmlDoc,
258 visited: &mut Vec<Vec<u8>>,
259) -> c_int {
260 if node.is_null() {
261 return 0;
262 }
263
264 let mut count: c_int = 0;
265
266 let node_type = unsafe { (*node).type_ };
271 if node_type == XML_XINCLUDE_START as c_int || node_type == XML_XINCLUDE_END as c_int {
272 if node_type == XML_XINCLUDE_START as c_int {
275 let mut child = unsafe { (*node).children };
276 while !child.is_null() {
277 count += unsafe { process_node_tree(child, doc, visited) };
278 child = unsafe { (*child).next };
279 }
280 }
281 return count;
282 }
283
284 let mut children: Vec<*mut _xmlNode> = Vec::new();
287 let mut child = unsafe { (*node).children };
288 while !child.is_null() {
289 children.push(child);
290 child = unsafe { (*child).next };
291 }
292
293 for child_node in children {
294 if unsafe { is_xinclude_element(child_node) } {
296 let processed = unsafe { process_single_include(child_node, doc, visited) };
297 if processed >= 0 {
298 count += processed;
299 } else {
300 count = -1; }
302 } else {
303 let child_type = unsafe { (*child_node).type_ };
305 if child_type == XML_ELEMENT_NODE as c_int
306 || child_type == XML_DOCUMENT_NODE as c_int
307 || child_type == XML_DOCUMENT_FRAG_NODE as c_int
308 || child_type == XML_XINCLUDE_START as c_int
309 {
310 count += unsafe { process_node_tree(child_node, doc, visited) };
311 }
312 }
313 }
314
315 count
316}
317
318unsafe fn is_xinclude_element(node: *mut _xmlNode) -> bool {
328 if node.is_null() {
329 return false;
330 }
331
332 let n = unsafe { &*node };
333 if n.type_ != XML_ELEMENT_NODE as c_int {
334 return false;
335 }
336
337 if n.name.is_null() {
338 return false;
339 }
340
341 let has_xinclude_ns = if !n.ns.is_null() {
343 let ns = unsafe { &*n.ns };
344 !ns.href.is_null()
345 && unsafe { xml_str_equal(ns.href, XINCLUDE_NS.as_ptr() as *const xmlChar) }
346 } else {
347 check_namespace_declaration(node, XINCLUDE_NS.as_ptr() as *const xmlChar)
351 };
352
353 if !has_xinclude_ns {
354 return false;
355 }
356
357 let name_bytes = unsafe { xmlstr_to_bytes(n.name) };
359 let local_name = if let Some(pos) = name_bytes.iter().position(|&b| b == b':') {
360 &name_bytes[pos + 1..]
361 } else {
362 name_bytes
363 };
364
365 local_name == b"include"
366}
367
368unsafe fn is_fallback_element(node: *mut _xmlNode) -> bool {
374 if node.is_null() {
375 return false;
376 }
377
378 let n = unsafe { &*node };
379 if n.type_ != XML_ELEMENT_NODE as c_int {
380 return false;
381 }
382
383 if n.name.is_null() {
384 return false;
385 }
386
387 let has_xinclude_ns = if !n.ns.is_null() {
389 let ns = unsafe { &*n.ns };
390 !ns.href.is_null()
391 && unsafe { xml_str_equal(ns.href, XINCLUDE_NS.as_ptr() as *const xmlChar) }
392 } else {
393 check_namespace_declaration(node, XINCLUDE_NS.as_ptr() as *const xmlChar)
394 };
395
396 if !has_xinclude_ns {
397 return false;
398 }
399
400 let name_bytes = unsafe { xmlstr_to_bytes(n.name) };
402 let local_name = if let Some(pos) = name_bytes.iter().position(|&b| b == b':') {
403 &name_bytes[pos + 1..]
404 } else {
405 name_bytes
406 };
407
408 local_name == b"fallback"
409}
410
411unsafe fn process_single_include(
419 include_node: *mut _xmlNode,
420 doc: *mut _xmlDoc,
421 visited: &mut Vec<Vec<u8>>,
422) -> c_int {
423 let href = unsafe { tree::get_prop(include_node, ATTR_HREF.as_ptr() as *const xmlChar) };
425
426 if href.is_null() {
428 return unsafe { apply_fallback(include_node, doc, visited) };
429 }
430
431 let href_str = unsafe { xmlstr_to_bytes(href) };
432
433 if visited.iter().any(|v| v.as_slice() == href_str) {
435 allocator::xmlFreeImpl(href as *mut c_void);
436 return unsafe { apply_fallback(include_node, doc, visited) };
437 }
438
439 let parse_attr = unsafe { tree::get_prop(include_node, ATTR_PARSE.as_ptr() as *const xmlChar) };
441 let is_text_mode = if !parse_attr.is_null() {
442 let parse_str = unsafe { xmlstr_to_bytes(parse_attr) };
443 let result = parse_str == b"text";
444 allocator::xmlFreeImpl(parse_attr as *mut c_void);
445 result
446 } else {
447 false
448 };
449
450 let xpointer_attr =
452 unsafe { tree::get_prop(include_node, ATTR_XPOINTER.as_ptr() as *const xmlChar) };
453
454 let accept_attr =
456 unsafe { tree::get_prop(include_node, ATTR_ACCEPT.as_ptr() as *const xmlChar) };
457
458 let accept_language_attr = unsafe {
460 tree::get_prop(
461 include_node,
462 ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar,
463 )
464 };
465
466 visited.push(href_str.to_vec());
468
469 let result = if is_text_mode {
470 unsafe { process_text_include(include_node, doc, href, accept_attr, visited) }
471 } else {
472 unsafe { process_xml_include(include_node, doc, href, xpointer_attr, visited) }
473 };
474
475 visited.pop();
477
478 allocator::xmlFreeImpl(href as *mut c_void);
480
481 if !parse_attr.is_null() {
482 allocator::xmlFreeImpl(parse_attr as *mut c_void);
483 }
484 if !xpointer_attr.is_null() {
485 allocator::xmlFreeImpl(xpointer_attr as *mut c_void);
486 }
487 if !accept_attr.is_null() {
488 allocator::xmlFreeImpl(accept_attr as *mut c_void);
489 }
490 if !accept_language_attr.is_null() {
491 allocator::xmlFreeImpl(accept_language_attr as *mut c_void);
492 }
493
494 match result {
495 Ok(processed) => processed,
496 Err(()) => unsafe { apply_fallback(include_node, doc, visited) },
497 }
498}
499
500unsafe fn process_text_include(
509 include_node: *mut _xmlNode,
510 doc: *mut _xmlDoc,
511 href: *mut xmlChar,
512 _accept: *mut xmlChar,
513 _visited: &mut Vec<Vec<u8>>,
514) -> Result<c_int, ()> {
515 let content = unsafe { io_read_file(href) };
517
518 if content.is_null() {
519 return Err(());
520 }
521
522 let encoding_attr =
524 unsafe { tree::get_prop(include_node, ATTR_ENCODING.as_ptr() as *const xmlChar) };
525
526 let text_node = unsafe { tree::new_text(content as *const xmlChar) };
528 if text_node.is_null() {
529 allocator::xmlFreeImpl(content as *mut c_void);
530 if !encoding_attr.is_null() {
531 allocator::xmlFreeImpl(encoding_attr as *mut c_void);
532 }
533 return Err(());
534 }
535
536 unsafe { replace_node_with_content(include_node, text_node, doc) };
538
539 allocator::xmlFreeImpl(content as *mut c_void);
540 if !encoding_attr.is_null() {
541 allocator::xmlFreeImpl(encoding_attr as *mut c_void);
542 }
543
544 Ok(1)
545}
546
547unsafe fn process_xml_include(
556 include_node: *mut _xmlNode,
557 doc: *mut _xmlDoc,
558 href: *mut xmlChar,
559 xpointer_attr: *mut xmlChar,
560 visited: &mut Vec<Vec<u8>>,
561) -> Result<c_int, ()> {
562 let included_doc = unsafe { parse_xml_document(href) };
564 if included_doc.is_null() {
565 return Err(());
566 }
567
568 let result = if !xpointer_attr.is_null() {
569 let xptr_str = unsafe { xmlstr_to_bytes(xpointer_attr) };
571 let xptr_utf8 = unsafe { std::str::from_utf8_unchecked(xptr_str) };
572 unsafe { include_via_xpointer(include_node, doc, included_doc, xptr_utf8, visited) }
573 } else {
574 unsafe { include_document_element(include_node, doc, included_doc, visited) }
576 };
577
578 let _ = unsafe { process_doc(included_doc, visited) };
580
581 unsafe { tree::free_doc(included_doc) };
584
585 result
586}
587
588unsafe fn include_document_element(
594 include_node: *mut _xmlNode,
595 doc: *mut _xmlDoc,
596 included_doc: *mut _xmlDoc,
597 _visited: &mut Vec<Vec<u8>>,
598) -> Result<c_int, ()> {
599 let root = unsafe { find_root_element(included_doc) };
600 if root.is_null() {
601 return Err(());
602 }
603
604 let copy = unsafe { tree::copy_node(root, 1) };
606 if copy.is_null() {
607 return Err(());
608 }
609
610 unsafe { set_doc_recursive(copy, doc) };
612
613 unsafe { replace_node_with_content(include_node, copy, doc) };
615
616 Ok(1)
617}
618
619unsafe fn include_via_xpointer(
625 include_node: *mut _xmlNode,
626 doc: *mut _xmlDoc,
627 included_doc: *mut _xmlDoc,
628 xpointer_expr: &str,
629 _visited: &mut Vec<Vec<u8>>,
630) -> Result<c_int, ()> {
631 let target = unsafe { xpointer::xptr_eval(xpointer_expr, included_doc) };
633
634 match target {
635 Some(target_node) => {
636 let copy = unsafe { tree::copy_node(target_node, 1) };
638 if copy.is_null() {
639 return Err(());
640 }
641
642 unsafe { set_doc_recursive(copy, doc) };
644
645 unsafe { replace_node_with_content(include_node, copy, doc) };
647
648 Ok(1)
649 }
650 None => Err(()),
651 }
652}
653
654unsafe fn apply_fallback(
662 include_node: *mut _xmlNode,
663 doc: *mut _xmlDoc,
664 visited: &mut Vec<Vec<u8>>,
665) -> c_int {
666 if include_node.is_null() {
667 return XINCLUDE_FAILURE;
668 }
669
670 let fallback = unsafe { find_fallback_child(include_node) };
672 if fallback.is_null() {
673 return 0; }
675
676 let mut fallback_children: Vec<*mut _xmlNode> = Vec::new();
678 let mut child = unsafe { (*fallback).children };
679 while !child.is_null() {
680 let next = unsafe { (*child).next };
681 fallback_children.push(child);
682 child = next;
683 }
684
685 if fallback_children.is_empty() {
686 unsafe { remove_node(include_node) };
688 return 1;
689 }
690
691 let parent = unsafe { (*include_node).parent };
693 if parent.is_null() {
694 return XINCLUDE_FAILURE;
695 }
696
697 let mut first_inserted: *mut _xmlNode = ptr::null_mut();
698 let mut last_inserted: *mut _xmlNode = ptr::null_mut();
699
700 for fb_child in &fallback_children {
701 let copy = unsafe { tree::copy_node(*fb_child, 1) };
702 if copy.is_null() {
703 continue;
704 }
705 unsafe { set_doc_recursive(copy, doc) };
706
707 unsafe {
709 let inserted = tree::add_sibling_before(include_node, copy);
710 if !inserted.is_null() {
711 if first_inserted.is_null() {
712 first_inserted = inserted;
713 }
714 last_inserted = inserted;
715 }
716 }
717 }
718
719 if !first_inserted.is_null() {
721 let mut cur = first_inserted;
722 loop {
723 unsafe {
724 let _ = process_node_tree(cur, doc, visited);
725 }
726 if cur == last_inserted {
727 break;
728 }
729 cur = unsafe { (*cur).next };
730 if cur.is_null() {
731 break;
732 }
733 }
734 }
735
736 unsafe { remove_node(include_node) };
738
739 1
740}
741
742unsafe fn find_fallback_child(node: *mut _xmlNode) -> *mut _xmlNode {
748 if node.is_null() {
749 return ptr::null_mut();
750 }
751
752 let mut child = unsafe { (*node).children };
753 while !child.is_null() {
754 if unsafe { is_fallback_element(child) } {
755 return child;
756 }
757 child = unsafe { (*child).next };
758 }
759
760 ptr::null_mut()
761}
762
763unsafe fn replace_node_with_content(
769 old_node: *mut _xmlNode,
770 new_content: *mut _xmlNode,
771 _doc: *mut _xmlDoc,
772) {
773 if old_node.is_null() || new_content.is_null() {
774 return;
775 }
776
777 let parent = unsafe { (*old_node).parent };
778 if parent.is_null() {
779 unsafe {
782 tree::add_sibling(old_node, new_content);
783 tree::unlink_node(old_node);
784 tree::free_node(old_node);
785 }
786 return;
787 }
788
789 unsafe {
791 tree::add_sibling_before(old_node, new_content);
792 tree::unlink_node(old_node);
793 tree::free_node(old_node);
794 }
795}
796
797unsafe fn remove_node(node: *mut _xmlNode) {
803 if node.is_null() {
804 return;
805 }
806 unsafe {
807 tree::unlink_node(node);
808 tree::free_node(node);
809 }
810}
811
812unsafe fn io_read_file(filename: *const xmlChar) -> *mut xmlChar {
820 if filename.is_null() {
821 return ptr::null_mut();
822 }
823
824 let c_filename = match std::ffi::CString::new(unsafe { xmlstr_to_bytes(filename) }) {
826 Ok(s) => s,
827 Err(_) => return ptr::null_mut(),
828 };
829
830 let fd = unsafe { libc::open(c_filename.as_ptr(), libc::O_RDONLY) };
831 if fd < 0 {
832 return ptr::null_mut();
833 }
834
835 let mut data = Vec::new();
836 let mut buf = [0u8; 4096];
837
838 loop {
839 let ret = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut c_void, buf.len()) };
840 if ret < 0 {
841 unsafe { libc::close(fd) };
842 return ptr::null_mut();
843 }
844 if ret == 0 {
845 break;
846 }
847 data.extend_from_slice(&buf[..ret as usize]);
848 }
849
850 unsafe { libc::close(fd) };
851
852 if data.is_empty() {
853 return ptr::null_mut();
854 }
855
856 let result = unsafe { allocator::xmlMallocImpl(data.len() + 1) as *mut xmlChar };
858 if result.is_null() {
859 return ptr::null_mut();
860 }
861
862 unsafe {
863 ptr::copy_nonoverlapping(data.as_ptr(), result, data.len());
864 *result.add(data.len()) = 0; }
866
867 result
868}
869
870unsafe fn parse_xml_document(filename: *const xmlChar) -> *mut _xmlDoc {
878 if filename.is_null() {
879 return ptr::null_mut();
880 }
881
882 let content = unsafe { io_read_file(filename) };
884 if content.is_null() {
885 return ptr::null_mut();
886 }
887
888 let content_bytes = unsafe { xmlstr_to_bytes(content) };
889 let size = content_bytes.len() as c_int;
890
891 let doc = unsafe {
893 crate::abi::exports_xml2::xmlReadMemory(
894 content as *const c_char,
895 size,
896 filename as *const c_char,
897 ptr::null(), 0, )
900 };
901
902 allocator::xmlFreeImpl(content as *mut c_void);
903
904 doc
905}
906
907unsafe fn find_root_element(doc: *mut _xmlDoc) -> *mut _xmlNode {
913 if doc.is_null() {
914 return ptr::null_mut();
915 }
916
917 let mut child = unsafe { (*doc).children };
918 while !child.is_null() {
919 let node_type = unsafe { (*child).type_ };
920 if node_type == XML_ELEMENT_NODE as c_int {
921 return child;
922 }
923 child = unsafe { (*child).next };
924 }
925
926 ptr::null_mut()
927}
928
929unsafe fn set_doc_recursive(node: *mut _xmlNode, doc: *mut _xmlDoc) {
936 if node.is_null() {
937 return;
938 }
939
940 unsafe {
941 (*node).doc = doc;
942 }
943
944 let mut child = unsafe { (*node).children };
946 while !child.is_null() {
947 unsafe { set_doc_recursive(child, doc) };
948 child = unsafe { (*child).next };
949 }
950
951 let mut prop = unsafe { (*node).properties };
953 while !prop.is_null() {
954 unsafe {
955 (*prop).doc = doc;
956 if !(*prop).children.is_null() {
957 set_doc_recursive((*prop).children, doc);
958 }
959 }
960 prop = unsafe { (*prop).next };
961 }
962}
963
964unsafe fn check_namespace_declaration(node: *mut _xmlNode, ns_uri: *const xmlChar) -> bool {
972 if node.is_null() {
973 return false;
974 }
975
976 let mut cur: *mut _xmlNode = node;
977 while !cur.is_null() {
978 let n = unsafe { &*cur };
979 let mut ns_def = n.nsDef;
980 while !ns_def.is_null() {
981 let ns = unsafe { &*ns_def };
982 if !ns.href.is_null() && unsafe { xml_str_equal(ns.href, ns_uri) } {
983 return true;
984 }
985 ns_def = ns.next;
986 }
987 cur = n.parent;
988 }
989
990 false
991}
992
993unsafe fn xml_str_equal(a: *const xmlChar, b: *const xmlChar) -> bool {
999 if a.is_null() && b.is_null() {
1000 return true;
1001 }
1002 if a.is_null() || b.is_null() {
1003 return false;
1004 }
1005 unsafe { crate::abi::exports_xml2::xmlStrEqual(a, b) != 0 }
1006}
1007
1008#[cfg(test)]
1013mod tests {
1014 use super::*;
1015 use crate::abi::allocator;
1016
1017 use crate::xml::tree;
1018 use std::os::raw::{c_char, c_int};
1019
1020 #[allow(dead_code)]
1026 unsafe fn create_doc_from_xml(xml: &[u8]) -> *mut _xmlDoc {
1027 let doc = unsafe {
1028 crate::abi::exports_xml2::xmlReadMemory(
1029 xml.as_ptr() as *const c_char,
1030 xml.len() as c_int,
1031 ptr::null(),
1032 ptr::null(),
1033 0,
1034 )
1035 };
1036 if doc.is_null() {
1037 return ptr::null_mut();
1038 }
1039 doc
1040 }
1041
1042 unsafe fn create_simple_doc() -> *mut _xmlDoc {
1044 let doc = tree::new_doc(ptr::null());
1045 assert!(!doc.is_null(), "Failed to create doc");
1046
1047 let root = tree::new_child(
1048 doc as *mut _xmlNode,
1049 ptr::null_mut(),
1050 c"root".as_ptr() as *const xmlChar,
1051 );
1052 assert!(!root.is_null(), "Failed to create root");
1053
1054 doc
1055 }
1056
1057 unsafe fn create_ns(
1059 node: *mut _xmlNode,
1060 prefix: *const xmlChar,
1061 href: *const xmlChar,
1062 ) -> *mut _xmlNs {
1063 tree::new_ns(node, href, prefix)
1064 }
1065
1066 unsafe fn create_doc_with_xinclude_ns() -> (*mut _xmlDoc, *mut _xmlNode) {
1068 let doc = tree::new_doc(ptr::null());
1069 assert!(!doc.is_null());
1070 let root = tree::new_child(
1071 doc as *mut _xmlNode,
1072 ptr::null_mut(),
1073 c"root".as_ptr() as *const xmlChar,
1074 );
1075 assert!(!root.is_null());
1076 create_ns(
1077 root,
1078 c"xi".as_ptr() as *const xmlChar,
1079 XINCLUDE_NS.as_ptr() as *const xmlChar,
1080 );
1081 (doc, root)
1082 }
1083
1084 unsafe fn create_include_child(
1086 parent: *mut _xmlNode,
1087 href: Option<&[u8]>,
1088 parse: Option<&[u8]>,
1089 ) -> *mut _xmlNode {
1090 let ns = create_ns(
1091 parent,
1092 c"xi".as_ptr() as *const xmlChar,
1093 XINCLUDE_NS.as_ptr() as *const xmlChar,
1094 );
1095 let elem = tree::new_child(parent, ns, c"include".as_ptr() as *const xmlChar);
1096 if let Some(h) = href {
1097 let h_str = crate::xml::string::bytes_to_xmlstr(h);
1098 tree::set_prop(elem, ATTR_HREF.as_ptr() as *const xmlChar, h_str);
1099 allocator::xmlFreeImpl(h_str as *mut c_void);
1100 }
1101 if let Some(p) = parse {
1102 let p_str = crate::xml::string::bytes_to_xmlstr(p);
1103 tree::set_prop(elem, ATTR_PARSE.as_ptr() as *const xmlChar, p_str);
1104 allocator::xmlFreeImpl(p_str as *mut c_void);
1105 }
1106 elem
1107 }
1108
1109 unsafe fn create_fallback_child(parent: *mut _xmlNode) -> *mut _xmlNode {
1111 let ns = create_ns(
1112 parent,
1113 c"xi".as_ptr() as *const xmlChar,
1114 XINCLUDE_NS.as_ptr() as *const xmlChar,
1115 );
1116 tree::new_child(parent, ns, c"fallback".as_ptr() as *const xmlChar)
1117 }
1118 #[allow(dead_code)]
1119 unsafe fn find_element(doc: *mut _xmlDoc, name: *const xmlChar) -> *mut _xmlNode {
1121 if doc.is_null() {
1122 return ptr::null_mut();
1123 }
1124 let mut child = unsafe { (*doc).children };
1125 while !child.is_null() {
1126 let result = unsafe { find_element_recursive(child, name) };
1127 if !result.is_null() {
1128 return result;
1129 }
1130 child = unsafe { (*child).next };
1131 }
1132 ptr::null_mut()
1133 }
1134
1135 #[allow(dead_code)]
1136 unsafe fn find_element_recursive(node: *mut _xmlNode, name: *const xmlChar) -> *mut _xmlNode {
1137 if node.is_null() {
1138 return ptr::null_mut();
1139 }
1140 let n = unsafe { &*node };
1141 if n.type_ == XML_ELEMENT_NODE as c_int
1142 && !n.name.is_null()
1143 && unsafe { xml_str_equal(n.name, name) }
1144 {
1145 return node;
1146 }
1147 let mut child = n.children;
1148 while !child.is_null() {
1149 let result = unsafe { find_element_recursive(child, name) };
1150 if !result.is_null() {
1151 return result;
1152 }
1153 child = unsafe { (*child).next };
1154 }
1155 ptr::null_mut()
1156 }
1157
1158 unsafe fn count_elements(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
1160 if doc.is_null() {
1161 return 0;
1162 }
1163 let mut count: c_int = 0;
1164 let mut child = unsafe { (*doc).children };
1165 while !child.is_null() {
1166 count += unsafe { count_elements_recursive(child, name) };
1167 child = unsafe { (*child).next };
1168 }
1169 count
1170 }
1171
1172 unsafe fn count_elements_recursive(node: *mut _xmlNode, name: *const xmlChar) -> c_int {
1173 if node.is_null() {
1174 return 0;
1175 }
1176 let mut count: c_int = 0;
1177 let n = unsafe { &*node };
1178 if n.type_ == XML_ELEMENT_NODE as c_int
1179 && !n.name.is_null()
1180 && unsafe { xml_str_equal(n.name, name) }
1181 {
1182 count += 1;
1183 }
1184 let mut child = n.children;
1185 while !child.is_null() {
1186 count += unsafe { count_elements_recursive(child, name) };
1187 child = unsafe { (*child).next };
1188 }
1189 count
1190 }
1191
1192 #[test]
1197 fn test_is_xinclude_element() {
1198 unsafe {
1199 let doc = create_simple_doc();
1200 assert!(!doc.is_null());
1201 let root = (*doc).children;
1202 assert!(!root.is_null());
1203 assert!(!is_xinclude_element(root));
1204 assert!(!is_xinclude_element(ptr::null_mut()));
1205 tree::free_doc(doc);
1206 }
1207 }
1208
1209 #[test]
1210 fn test_xinclude_namespace_detection() {
1211 unsafe {
1212 let (doc, root) = create_doc_with_xinclude_ns();
1213 let include = create_include_child(root, Some(b"test.xml"), None);
1214 assert!(!include.is_null());
1215 assert!(is_xinclude_element(include), "Should detect xi:include");
1216
1217 let regular =
1219 tree::new_child(root, ptr::null_mut(), c"regular".as_ptr() as *const xmlChar);
1220 assert!(!regular.is_null());
1221 assert!(!is_xinclude_element(regular), "Regular elem not xinclude");
1222
1223 tree::free_doc(doc);
1224 }
1225 }
1226
1227 #[test]
1228 fn test_find_fallback_child() {
1229 unsafe {
1230 let (doc, root) = create_doc_with_xinclude_ns();
1231 let include = create_include_child(root, None, None);
1232 assert!(!include.is_null());
1233 let fallback = create_fallback_child(include);
1234 assert!(!fallback.is_null());
1235
1236 let found = find_fallback_child(include);
1237 assert!(!found.is_null(), "Should find fallback child");
1238
1239 let no_fallback = find_fallback_child(root);
1240 assert!(no_fallback.is_null(), "Root should not have fallback");
1241
1242 tree::free_doc(doc);
1243 }
1244 }
1245
1246 #[test]
1247 fn test_xml_str_equal() {
1248 unsafe {
1249 assert!(xml_str_equal(
1250 c"hello".as_ptr() as *const xmlChar,
1251 c"hello".as_ptr() as *const xmlChar,
1252 ));
1253 assert!(!xml_str_equal(
1254 c"hello".as_ptr() as *const xmlChar,
1255 c"world".as_ptr() as *const xmlChar,
1256 ));
1257 assert!(!xml_str_equal(
1258 ptr::null(),
1259 c"hello".as_ptr() as *const xmlChar
1260 ));
1261 assert!(!xml_str_equal(
1262 c"hello".as_ptr() as *const xmlChar,
1263 ptr::null()
1264 ));
1265 assert!(xml_str_equal(ptr::null(), ptr::null()));
1266 }
1267 }
1268
1269 #[test]
1270 fn test_xinclude_process_null_doc() {
1271 unsafe {
1272 assert_eq!(xinclude_process(ptr::null_mut()), XINCLUDE_FAILURE);
1273 }
1274 }
1275
1276 #[test]
1277 fn test_xinclude_process_no_includes() {
1278 unsafe {
1279 let doc = create_simple_doc();
1280 assert_eq!(xinclude_process(doc), 0);
1281 tree::free_doc(doc);
1282 }
1283 }
1284
1285 #[test]
1286 fn test_xinclude_process_with_includes() {
1287 unsafe {
1288 let (doc, root) = create_doc_with_xinclude_ns();
1290 create_include_child(root, Some(b"nonexistent.xml"), None);
1291 let result = xinclude_process(doc);
1292 assert!(result >= 0, "Should handle missing files: {}", result);
1293 tree::free_doc(doc);
1294 }
1295 }
1296
1297 #[test]
1298 fn test_xinclude_fallback_content() {
1299 unsafe {
1300 let (doc, root) = create_doc_with_xinclude_ns();
1301 let include = create_include_child(root, Some(b"nonexistent.xml"), None);
1302 let fb = create_fallback_child(include);
1303 let fb_child = tree::new_child(
1305 fb,
1306 ptr::null_mut(),
1307 c"fallback-elem".as_ptr() as *const xmlChar,
1308 );
1309 assert!(!fb_child.is_null());
1310
1311 let before = count_elements(doc, c"fallback-elem".as_ptr() as *const xmlChar);
1312 assert!(before > 0, "Should have fallback-elem before processing");
1313
1314 let result = xinclude_process(doc);
1315 assert!(result >= 0, "Should handle fallback: {}", result);
1316 tree::free_doc(doc);
1317 }
1318 }
1319
1320 #[test]
1321 fn test_xinclude_circular_reference_detection() {
1322 unsafe {
1323 let (doc, root) = create_doc_with_xinclude_ns();
1324 create_include_child(root, Some(b"self-ref.xml"), None);
1325 let result = xinclude_process(doc);
1326 assert!(result >= 0, "Circular ref should not crash: {}", result);
1327 tree::free_doc(doc);
1328 }
1329 }
1330
1331 #[test]
1332 fn test_xinclude_parse_attribute_detection() {
1333 unsafe {
1334 let (doc, root) = create_doc_with_xinclude_ns();
1335 create_include_child(root, Some(b"test.xml"), Some(b"xml"));
1336 create_include_child(root, Some(b"test.txt"), Some(b"text"));
1337 create_include_child(root, Some(b"default.xml"), None);
1338
1339 let mut count = 0;
1341 let mut child = (*root).children;
1342 while !child.is_null() {
1343 if is_xinclude_element(child) {
1344 count += 1;
1345 }
1346 child = (*child).next;
1347 }
1348 assert_eq!(count, 3, "Should have 3 include elements");
1349 tree::free_doc(doc);
1350 }
1351 }
1352
1353 #[test]
1354 fn test_xinclude_process_functions() {
1355 unsafe {
1356 let doc = create_simple_doc();
1357 let r1 = xinclude_process(doc);
1358 assert!(r1 >= 0);
1359 let r2 = xinclude_process_flags(doc, 0);
1360 assert!(r2 >= 0);
1361 tree::free_doc(doc);
1362 }
1363 }
1364
1365 #[test]
1366 fn test_xinclude_process_with_empty_href() {
1367 unsafe {
1368 let (doc, root) = create_doc_with_xinclude_ns();
1369 let include = create_include_child(root, None, None);
1370 create_fallback_child(include);
1371 let result = xinclude_process(doc);
1372 assert!(result >= 0, "Empty href with fallback: {}", result);
1373 tree::free_doc(doc);
1374 }
1375 }
1376
1377 #[test]
1378 fn test_set_doc_recursive() {
1379 unsafe {
1380 let doc = tree::new_doc(ptr::null());
1381 assert!(!doc.is_null());
1382 let parent = tree::new_child(
1383 doc as *mut _xmlNode,
1384 ptr::null_mut(),
1385 c"parent".as_ptr() as *const xmlChar,
1386 );
1387 assert!(!parent.is_null());
1388 let detached = tree::new_node(ptr::null_mut(), c"detached".as_ptr() as *const xmlChar);
1389 assert!(!detached.is_null());
1390 assert!((*detached).doc.is_null());
1391 set_doc_recursive(detached, doc);
1392 assert_eq!((*detached).doc, doc);
1393 tree::free_node(detached);
1394 tree::free_doc(doc);
1395 }
1396 }
1397
1398 #[test]
1399 fn test_find_root_element() {
1400 unsafe {
1401 let doc = create_simple_doc();
1402 let root = find_root_element(doc);
1403 assert!(!root.is_null());
1404 assert_eq!((*root).type_, XML_ELEMENT_NODE as c_int);
1405 tree::free_doc(doc);
1406 }
1407 }
1408
1409 #[test]
1410 fn test_xinclude_xpointer_attribute() {
1411 unsafe {
1412 let (doc, root) = create_doc_with_xinclude_ns();
1413 let include = create_include_child(root, Some(b"test.xml"), None);
1414 let xptr_val = crate::xml::string::bytes_to_xmlstr(b"xpointer(//target)");
1415 tree::set_prop(include, ATTR_XPOINTER.as_ptr() as *const xmlChar, xptr_val);
1416 allocator::xmlFreeImpl(xptr_val as *mut c_void);
1417
1418 let xptr = tree::get_prop(include, ATTR_XPOINTER.as_ptr() as *const xmlChar);
1419 assert!(!xptr.is_null(), "Should have xpointer attribute");
1420 assert_eq!(xmlstr_to_bytes(xptr), b"xpointer(//target)");
1421 allocator::xmlFreeImpl(xptr as *mut c_void);
1422
1423 tree::free_doc(doc);
1424 }
1425 }
1426
1427 #[test]
1428 fn test_xinclude_accept_attributes() {
1429 unsafe {
1430 let (doc, root) = create_doc_with_xinclude_ns();
1431 let include = create_include_child(root, Some(b"data.xml"), None);
1432
1433 let accept_val = crate::xml::string::bytes_to_xmlstr(b"application/xml");
1434 tree::set_prop(include, ATTR_ACCEPT.as_ptr() as *const xmlChar, accept_val);
1435 allocator::xmlFreeImpl(accept_val as *mut c_void);
1436
1437 let lang_val = crate::xml::string::bytes_to_xmlstr(b"en");
1438 tree::set_prop(
1439 include,
1440 ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar,
1441 lang_val,
1442 );
1443 allocator::xmlFreeImpl(lang_val as *mut c_void);
1444
1445 let accept = tree::get_prop(include, ATTR_ACCEPT.as_ptr() as *const xmlChar);
1446 assert!(!accept.is_null());
1447 assert_eq!(xmlstr_to_bytes(accept), b"application/xml");
1448 allocator::xmlFreeImpl(accept as *mut c_void);
1449
1450 let lang = tree::get_prop(include, ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar);
1451 assert!(!lang.is_null());
1452 assert_eq!(xmlstr_to_bytes(lang), b"en");
1453 allocator::xmlFreeImpl(lang as *mut c_void);
1454
1455 tree::free_doc(doc);
1456 }
1457 }
1458
1459 #[test]
1460 fn test_xinclude_encoding_attribute() {
1461 unsafe {
1462 let (doc, root) = create_doc_with_xinclude_ns();
1463 let include = create_include_child(root, Some(b"data.txt"), Some(b"text"));
1464
1465 let enc_val = crate::xml::string::bytes_to_xmlstr(b"UTF-8");
1466 tree::set_prop(include, ATTR_ENCODING.as_ptr() as *const xmlChar, enc_val);
1467 allocator::xmlFreeImpl(enc_val as *mut c_void);
1468
1469 let encoding = tree::get_prop(include, ATTR_ENCODING.as_ptr() as *const xmlChar);
1470 assert!(!encoding.is_null());
1471 assert_eq!(xmlstr_to_bytes(encoding), b"UTF-8");
1472 allocator::xmlFreeImpl(encoding as *mut c_void);
1473
1474 tree::free_doc(doc);
1475 }
1476 }
1477
1478 #[test]
1479 fn test_xinclude_process_flags_equivalence() {
1480 unsafe {
1481 let doc = create_simple_doc();
1482 let r1 = xinclude_process(doc);
1483 let r2 = xinclude_process_flags(doc, 0);
1484 assert_eq!(r1, r2);
1485 tree::free_doc(doc);
1486 }
1487 }
1488
1489 #[test]
1490 fn test_xinclude_process_flags_noxincnode() {
1491 unsafe {
1492 let doc = create_simple_doc();
1493 let result = xinclude_process_flags(doc, XML_PARSE_NOXINCNODE);
1494 assert_eq!(result, 0);
1495 tree::free_doc(doc);
1496 }
1497 }
1498
1499 #[test]
1500 #[ignore = "pre-existing tree module cleanup bug with modified trees"]
1501 fn test_complex_nested_includes_structure() {
1502 unsafe {
1503 let doc = tree::new_doc(ptr::null());
1505 assert!(!doc.is_null());
1506 let root = tree::new_child(
1507 doc as *mut _xmlNode,
1508 ptr::null_mut(),
1509 c"root".as_ptr() as *const xmlChar,
1510 );
1511 assert!(!root.is_null());
1512 create_ns(
1513 root,
1514 c"xi".as_ptr() as *const xmlChar,
1515 XINCLUDE_NS.as_ptr() as *const xmlChar,
1516 );
1517
1518 create_include_child(root, Some(b"nonexistent1.xml"), None);
1519
1520 let inc2 = create_include_child(root, Some(b"nonexistent2.xml"), None);
1521 let fb2 = create_fallback_child(inc2);
1522 tree::new_child(
1523 fb2,
1524 ptr::null_mut(),
1525 c"fallback-content".as_ptr() as *const xmlChar,
1526 );
1527
1528 create_include_child(root, Some(b"nonexistent3.txt"), Some(b"text"));
1529
1530 let result = xinclude_process(doc);
1531 assert!(result >= 0, "Complex structure: {}", result);
1532 }
1533 }
1534
1535 #[test]
1536 fn test_xinclude_process_xml_memory_cleanup() {
1537 unsafe {
1538 let doc = create_simple_doc();
1539 assert!(xinclude_process(doc) >= 0);
1540 tree::free_doc(doc);
1541 }
1542 }
1543
1544 #[test]
1545 fn test_mark_doc_xinclude_processed() {
1546 unsafe {
1547 let doc = create_simple_doc();
1548 assert_eq!((*doc).properties & XML_DOC_XINCLUDE as c_int, 0);
1549 mark_doc_xinclude_processed(doc);
1550 assert_ne!((*doc).properties & XML_DOC_XINCLUDE as c_int, 0);
1551 tree::free_doc(doc);
1552 }
1553 }
1554
1555 #[test]
1556 fn test_xinclude_xinclude_start_end_nodes() {
1557 unsafe {
1558 let doc = create_simple_doc();
1559 let root = find_root_element(doc);
1560 assert!(!root.is_null());
1561
1562 let sentinel =
1564 tree::new_node(ptr::null_mut(), c"XIncludeStart".as_ptr() as *const xmlChar);
1565 assert!(!sentinel.is_null());
1566 (*sentinel).type_ = XML_XINCLUDE_START as c_int;
1567 (*sentinel).doc = doc;
1568 let first_child = (*root).children;
1570 if !first_child.is_null() {
1571 (*sentinel).parent = root;
1573 (*sentinel).prev = first_child;
1574 (*sentinel).next = (*first_child).next;
1575 if !(*first_child).next.is_null() {
1576 (*(*first_child).next).prev = sentinel;
1577 }
1578 (*first_child).next = sentinel;
1579 if (*root).last == first_child {
1580 (*root).last = sentinel;
1581 }
1582 }
1583
1584 let mut visited = Vec::new();
1585 let count = { process_node_tree(root, doc, &mut visited) };
1586 assert_eq!(count, 0, "Should not process sentinel nodes");
1587
1588 if !(*sentinel).prev.is_null() {
1590 (*(*sentinel).prev).next = (*sentinel).next;
1591 }
1592 if !(*sentinel).next.is_null() {
1593 (*(*sentinel).next).prev = (*sentinel).prev;
1594 }
1595 (*sentinel).prev = ptr::null_mut();
1596 (*sentinel).next = ptr::null_mut();
1597 (*sentinel).parent = ptr::null_mut();
1598
1599 tree::free_node(sentinel);
1600 tree::free_doc(doc);
1601 }
1602 }
1603}