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/2003/XInclude\0";
103
104const XINCLUDE_OLD_NS: &[u8] = b"http://www.w3.org/2001/XInclude\0";
106
107#[allow(dead_code)]
109const XINCLUDE_INCLUDE: &[u8] = b"include\0";
110
111#[allow(dead_code)]
113const XINCLUDE_FALLBACK: &[u8] = b"fallback\0";
114
115const ATTR_HREF: &[u8] = b"href\0";
117
118const ATTR_PARSE: &[u8] = b"parse\0";
120
121const ATTR_XPOINTER: &[u8] = b"xpointer\0";
123
124const ATTR_ENCODING: &[u8] = b"encoding\0";
126
127const ATTR_ACCEPT: &[u8] = b"accept\0";
129
130const ATTR_ACCEPT_LANGUAGE: &[u8] = b"accept-language\0";
132
133#[allow(dead_code)]
139const XINCLUDE_SUCCESS: c_int = 0;
140
141const XINCLUDE_FAILURE: c_int = -1;
143
144const XINCLUDE_NO_NODES: c_int = 0;
146
147#[allow(dead_code)]
153const XML_XINCLUDE_NO_INCLUDE: c_int = 0;
154
155pub unsafe fn xinclude_process(doc: *mut _xmlDoc) -> c_int {
168 if doc.is_null() {
169 return XINCLUDE_FAILURE;
170 }
171
172 let mut visited: Vec<Vec<u8>> = Vec::new();
174
175 let count = unsafe { process_doc(doc, &mut visited) };
176
177 if count > 0 {
178 unsafe { mark_doc_xinclude_processed(doc) };
179 }
180
181 count
182}
183
184pub unsafe fn xinclude_process_flags(doc: *mut _xmlDoc, flags: c_int) -> c_int {
194 if doc.is_null() {
195 return XINCLUDE_FAILURE;
196 }
197
198 let _ = flags;
204
205 let mut visited: Vec<Vec<u8>> = Vec::new();
207
208 let count = unsafe { process_doc(doc, &mut visited) };
209
210 if count > 0 {
211 unsafe { mark_doc_xinclude_processed(doc) };
212 }
213
214 count
215}
216
217unsafe fn mark_doc_xinclude_processed(doc: *mut _xmlDoc) {
227 unsafe {
228 let d = &mut *doc;
229 d.properties |= XML_DOC_XINCLUDE as c_int;
230 }
231}
232
233unsafe fn process_doc(doc: *mut _xmlDoc, visited: &mut Vec<Vec<u8>>) -> c_int {
240 let mut count: c_int = 0;
241
242 let root = unsafe { find_root_element(doc) };
244 if root.is_null() {
245 return XINCLUDE_NO_NODES;
246 }
247
248 unsafe {
250 count += process_node_tree(root, doc, visited);
251 }
252
253 count
254}
255
256unsafe fn process_node_tree(
264 node: *mut _xmlNode,
265 doc: *mut _xmlDoc,
266 visited: &mut Vec<Vec<u8>>,
267) -> c_int {
268 if node.is_null() {
269 return 0;
270 }
271
272 let mut count: c_int = 0;
273
274 let node_type = unsafe { (*node).type_ };
279 if node_type == XML_XINCLUDE_START as c_int || node_type == XML_XINCLUDE_END as c_int {
280 if node_type == XML_XINCLUDE_START as c_int {
283 let mut child = unsafe { (*node).children };
284 while !child.is_null() {
285 count += unsafe { process_node_tree(child, doc, visited) };
286 child = unsafe { (*child).next };
287 }
288 }
289 return count;
290 }
291
292 let mut children: Vec<*mut _xmlNode> = Vec::new();
295 let mut child = unsafe { (*node).children };
296 while !child.is_null() {
297 children.push(child);
298 child = unsafe { (*child).next };
299 }
300
301 for child_node in children {
302 if unsafe { is_xinclude_element(child_node) } {
304 let processed = unsafe { process_single_include(child_node, doc, visited) };
305 if processed >= 0 {
306 count += processed;
307 } else {
308 count = -1; }
310 } else {
311 let child_type = unsafe { (*child_node).type_ };
313 if child_type == XML_ELEMENT_NODE as c_int
314 || child_type == XML_DOCUMENT_NODE as c_int
315 || child_type == XML_DOCUMENT_FRAG_NODE as c_int
316 || child_type == XML_XINCLUDE_START as c_int
317 {
318 count += unsafe { process_node_tree(child_node, doc, visited) };
319 }
320 }
321 }
322
323 count
324}
325
326unsafe fn is_xinclude_element(node: *mut _xmlNode) -> bool {
336 if node.is_null() {
337 return false;
338 }
339
340 let n = unsafe { &*node };
341 if n.type_ != XML_ELEMENT_NODE as c_int {
342 return false;
343 }
344
345 if n.name.is_null() {
346 return false;
347 }
348
349 let has_xinclude_ns = if !n.ns.is_null() {
351 let ns = unsafe { &*n.ns };
352 !ns.href.is_null() && unsafe { is_xinclude_ns_uri(ns.href) }
353 } else {
354 check_namespace_declaration(node, XINCLUDE_NS.as_ptr() as *const xmlChar)
358 || check_namespace_declaration(node, XINCLUDE_OLD_NS.as_ptr() as *const xmlChar)
359 };
360
361 if !has_xinclude_ns {
362 return false;
363 }
364
365 let name_bytes = unsafe { xmlstr_to_bytes(n.name) };
367 let local_name = if let Some(pos) = name_bytes.iter().position(|&b| b == b':') {
368 &name_bytes[pos + 1..]
369 } else {
370 name_bytes
371 };
372
373 local_name == b"include"
374}
375
376unsafe fn is_fallback_element(node: *mut _xmlNode) -> bool {
382 if node.is_null() {
383 return false;
384 }
385
386 let n = unsafe { &*node };
387 if n.type_ != XML_ELEMENT_NODE as c_int {
388 return false;
389 }
390
391 if n.name.is_null() {
392 return false;
393 }
394
395 let has_xinclude_ns = if !n.ns.is_null() {
397 let ns = unsafe { &*n.ns };
398 !ns.href.is_null() && unsafe { is_xinclude_ns_uri(ns.href) }
399 } else {
400 check_namespace_declaration(node, XINCLUDE_NS.as_ptr() as *const xmlChar)
401 || check_namespace_declaration(node, XINCLUDE_OLD_NS.as_ptr() as *const xmlChar)
402 };
403
404 if !has_xinclude_ns {
405 return false;
406 }
407
408 let name_bytes = unsafe { xmlstr_to_bytes(n.name) };
410 let local_name = if let Some(pos) = name_bytes.iter().position(|&b| b == b':') {
411 &name_bytes[pos + 1..]
412 } else {
413 name_bytes
414 };
415
416 local_name == b"fallback"
417}
418
419unsafe fn process_single_include(
427 include_node: *mut _xmlNode,
428 doc: *mut _xmlDoc,
429 visited: &mut Vec<Vec<u8>>,
430) -> c_int {
431 let href = unsafe { tree::get_prop(include_node, ATTR_HREF.as_ptr() as *const xmlChar) };
433
434 let xpointer_attr =
436 unsafe { tree::get_prop(include_node, ATTR_XPOINTER.as_ptr() as *const xmlChar) };
437
438 if href.is_null() {
440 if !xpointer_attr.is_null() {
445 let xptr_bytes = unsafe { xmlstr_to_bytes(xpointer_attr) };
446 let xptr_utf8 = match std::str::from_utf8(&xptr_bytes) {
447 Ok(s) => s.to_string(),
448 Err(_) => {
449 allocator::xmlFreeImpl(xpointer_attr as *mut c_void);
450 return unsafe { apply_fallback(include_node, doc, visited) };
451 }
452 };
453 allocator::xmlFreeImpl(xpointer_attr as *mut c_void);
454 if let Some(target) = unsafe { xpointer::xptr_eval(&xptr_utf8, doc) } {
455 let copy = unsafe { tree::copy_node(target, 1) };
456 if copy.is_null() {
457 return unsafe { apply_fallback(include_node, doc, visited) };
458 }
459 unsafe { set_doc_recursive(copy, doc) };
460 unsafe { replace_node_with_content(include_node, copy, doc) };
461 return 1;
462 }
463 } else {
464 if !xpointer_attr.is_null() {
465 allocator::xmlFreeImpl(xpointer_attr as *mut c_void);
466 }
467 }
468 return unsafe { apply_fallback(include_node, doc, visited) };
469 }
470
471 let href_str = unsafe { xmlstr_to_bytes(href) };
472
473 if visited.iter().any(|v| v.as_slice() == href_str) {
475 allocator::xmlFreeImpl(href as *mut c_void);
476 if !xpointer_attr.is_null() {
477 allocator::xmlFreeImpl(xpointer_attr as *mut c_void);
478 }
479 return unsafe { apply_fallback(include_node, doc, visited) };
480 }
481
482 let mut parse_attr =
484 unsafe { tree::get_prop(include_node, ATTR_PARSE.as_ptr() as *const xmlChar) };
485 let is_text_mode = if !parse_attr.is_null() {
486 let parse_str = unsafe { xmlstr_to_bytes(parse_attr) };
487 let result = parse_str == b"text";
488 allocator::xmlFreeImpl(parse_attr as *mut c_void);
493 parse_attr = ptr::null_mut();
494 result
495 } else {
496 false
497 };
498
499 let accept_attr =
501 unsafe { tree::get_prop(include_node, ATTR_ACCEPT.as_ptr() as *const xmlChar) };
502
503 let accept_language_attr = unsafe {
505 tree::get_prop(
506 include_node,
507 ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar,
508 )
509 };
510
511 visited.push(href_str.to_vec());
513
514 let result = if is_text_mode {
515 unsafe { process_text_include(include_node, doc, href, accept_attr, visited) }
516 } else {
517 unsafe { process_xml_include(include_node, doc, href, xpointer_attr, visited) }
518 };
519
520 visited.pop();
522
523 allocator::xmlFreeImpl(href as *mut c_void);
525
526 if !parse_attr.is_null() {
527 allocator::xmlFreeImpl(parse_attr as *mut c_void);
528 }
529 if !xpointer_attr.is_null() {
530 allocator::xmlFreeImpl(xpointer_attr as *mut c_void);
531 }
532 if !accept_attr.is_null() {
533 allocator::xmlFreeImpl(accept_attr as *mut c_void);
534 }
535 if !accept_language_attr.is_null() {
536 allocator::xmlFreeImpl(accept_language_attr as *mut c_void);
537 }
538
539 match result {
540 Ok(processed) => processed,
541 Err(()) => unsafe { apply_fallback(include_node, doc, visited) },
542 }
543}
544
545unsafe fn process_text_include(
554 include_node: *mut _xmlNode,
555 doc: *mut _xmlDoc,
556 href: *mut xmlChar,
557 _accept: *mut xmlChar,
558 _visited: &mut Vec<Vec<u8>>,
559) -> Result<c_int, ()> {
560 let content = unsafe { io_read_file(href) };
562
563 if content.is_null() {
564 return Err(());
565 }
566
567 let encoding_attr =
569 unsafe { tree::get_prop(include_node, ATTR_ENCODING.as_ptr() as *const xmlChar) };
570
571 let text_node = unsafe { tree::new_text(content as *const xmlChar) };
573 if text_node.is_null() {
574 allocator::xmlFreeImpl(content as *mut c_void);
575 if !encoding_attr.is_null() {
576 allocator::xmlFreeImpl(encoding_attr as *mut c_void);
577 }
578 return Err(());
579 }
580
581 unsafe { replace_node_with_content(include_node, text_node, doc) };
583
584 allocator::xmlFreeImpl(content as *mut c_void);
585 if !encoding_attr.is_null() {
586 allocator::xmlFreeImpl(encoding_attr as *mut c_void);
587 }
588
589 Ok(1)
590}
591
592unsafe fn process_xml_include(
601 include_node: *mut _xmlNode,
602 doc: *mut _xmlDoc,
603 href: *mut xmlChar,
604 xpointer_attr: *mut xmlChar,
605 visited: &mut Vec<Vec<u8>>,
606) -> Result<c_int, ()> {
607 let included_doc = unsafe { parse_xml_document(href) };
609 if included_doc.is_null() {
610 return Err(());
611 }
612
613 let result = if !xpointer_attr.is_null() {
614 let xptr_str = unsafe { xmlstr_to_bytes(xpointer_attr) };
616 let xptr_utf8 = unsafe { std::str::from_utf8_unchecked(xptr_str) };
617 unsafe { include_via_xpointer(include_node, doc, included_doc, xptr_utf8, visited) }
618 } else {
619 unsafe { include_document_element(include_node, doc, included_doc, visited) }
621 };
622
623 let _ = unsafe { process_doc(included_doc, visited) };
625
626 unsafe { tree::free_doc(included_doc) };
629
630 result
631}
632
633unsafe fn include_document_element(
639 include_node: *mut _xmlNode,
640 doc: *mut _xmlDoc,
641 included_doc: *mut _xmlDoc,
642 _visited: &mut Vec<Vec<u8>>,
643) -> Result<c_int, ()> {
644 let root = unsafe { find_root_element(included_doc) };
645 if root.is_null() {
646 return Err(());
647 }
648
649 let copy = unsafe { tree::copy_node(root, 1) };
651 if copy.is_null() {
652 return Err(());
653 }
654
655 unsafe { set_doc_recursive(copy, doc) };
657
658 unsafe { replace_node_with_content(include_node, copy, doc) };
660
661 Ok(1)
662}
663
664unsafe fn include_via_xpointer(
670 include_node: *mut _xmlNode,
671 doc: *mut _xmlDoc,
672 included_doc: *mut _xmlDoc,
673 xpointer_expr: &str,
674 _visited: &mut Vec<Vec<u8>>,
675) -> Result<c_int, ()> {
676 let target = unsafe { xpointer::xptr_eval(xpointer_expr, included_doc) };
678
679 match target {
680 Some(target_node) => {
681 let copy = unsafe { tree::copy_node(target_node, 1) };
683 if copy.is_null() {
684 return Err(());
685 }
686
687 unsafe { set_doc_recursive(copy, doc) };
689
690 unsafe { replace_node_with_content(include_node, copy, doc) };
692
693 Ok(1)
694 }
695 None => Err(()),
696 }
697}
698
699unsafe fn apply_fallback(
707 include_node: *mut _xmlNode,
708 doc: *mut _xmlDoc,
709 visited: &mut Vec<Vec<u8>>,
710) -> c_int {
711 if include_node.is_null() {
712 return XINCLUDE_FAILURE;
713 }
714
715 let fallback = unsafe { find_fallback_child(include_node) };
717 if fallback.is_null() {
718 return 0; }
720
721 let mut fallback_children: Vec<*mut _xmlNode> = Vec::new();
723 let mut child = unsafe { (*fallback).children };
724 while !child.is_null() {
725 let next = unsafe { (*child).next };
726 fallback_children.push(child);
727 child = next;
728 }
729
730 if fallback_children.is_empty() {
731 unsafe { remove_node(include_node) };
733 return 1;
734 }
735
736 let parent = unsafe { (*include_node).parent };
738 if parent.is_null() {
739 return XINCLUDE_FAILURE;
740 }
741
742 let mut first_inserted: *mut _xmlNode = ptr::null_mut();
743 let mut last_inserted: *mut _xmlNode = ptr::null_mut();
744
745 for fb_child in &fallback_children {
746 let copy = unsafe { tree::copy_node(*fb_child, 1) };
747 if copy.is_null() {
748 continue;
749 }
750 unsafe { set_doc_recursive(copy, doc) };
751
752 unsafe {
754 let inserted = tree::add_sibling_before(include_node, copy);
755 if !inserted.is_null() {
756 if first_inserted.is_null() {
757 first_inserted = inserted;
758 }
759 last_inserted = inserted;
760 }
761 }
762 }
763
764 if !first_inserted.is_null() {
766 let mut cur = first_inserted;
767 loop {
768 unsafe {
769 let _ = process_node_tree(cur, doc, visited);
770 }
771 if cur == last_inserted {
772 break;
773 }
774 cur = unsafe { (*cur).next };
775 if cur.is_null() {
776 break;
777 }
778 }
779 }
780
781 unsafe { remove_node(include_node) };
783
784 1
785}
786
787unsafe fn find_fallback_child(node: *mut _xmlNode) -> *mut _xmlNode {
793 if node.is_null() {
794 return ptr::null_mut();
795 }
796
797 let mut child = unsafe { (*node).children };
798 while !child.is_null() {
799 if unsafe { is_fallback_element(child) } {
800 return child;
801 }
802 child = unsafe { (*child).next };
803 }
804
805 ptr::null_mut()
806}
807
808unsafe fn replace_node_with_content(
814 old_node: *mut _xmlNode,
815 new_content: *mut _xmlNode,
816 _doc: *mut _xmlDoc,
817) {
818 if old_node.is_null() || new_content.is_null() {
819 return;
820 }
821
822 let parent = unsafe { (*old_node).parent };
823 if parent.is_null() {
824 unsafe {
827 tree::add_sibling(old_node, new_content);
828 tree::unlink_node(old_node);
829 tree::free_node(old_node);
830 }
831 return;
832 }
833
834 unsafe {
836 tree::add_sibling_before(old_node, new_content);
837 tree::unlink_node(old_node);
838 tree::free_node(old_node);
839 }
840}
841
842unsafe fn remove_node(node: *mut _xmlNode) {
848 if node.is_null() {
849 return;
850 }
851 unsafe {
852 tree::unlink_node(node);
853 tree::free_node(node);
854 }
855}
856
857unsafe fn io_read_file(filename: *const xmlChar) -> *mut xmlChar {
865 if filename.is_null() {
866 return ptr::null_mut();
867 }
868
869 let c_filename = match std::ffi::CString::new(unsafe { xmlstr_to_bytes(filename) }) {
871 Ok(s) => s,
872 Err(_) => return ptr::null_mut(),
873 };
874
875 if let Some(data) =
880 crate::abi::exports_parser::read_uri_via_input_callbacks(c_filename.as_ptr())
881 {
882 if data.is_empty() {
883 return ptr::null_mut();
884 }
885 let result = unsafe { allocator::xmlMallocImpl(data.len() + 1) as *mut xmlChar };
886 if result.is_null() {
887 return ptr::null_mut();
888 }
889 unsafe {
890 ptr::copy_nonoverlapping(data.as_ptr(), result, data.len());
891 *result.add(data.len()) = 0; }
893 return result;
894 }
895
896 let fd = unsafe { libc::open(c_filename.as_ptr(), libc::O_RDONLY) };
897 if fd < 0 {
898 return ptr::null_mut();
899 }
900
901 let mut data = Vec::new();
902 let mut buf = [0u8; 4096];
903
904 loop {
905 let ret = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut c_void, buf.len()) };
906 if ret < 0 {
907 unsafe { libc::close(fd) };
908 return ptr::null_mut();
909 }
910 if ret == 0 {
911 break;
912 }
913 data.extend_from_slice(&buf[..ret as usize]);
914 }
915
916 unsafe { libc::close(fd) };
917
918 if data.is_empty() {
919 return ptr::null_mut();
920 }
921
922 let result = unsafe { allocator::xmlMallocImpl(data.len() + 1) as *mut xmlChar };
924 if result.is_null() {
925 return ptr::null_mut();
926 }
927
928 unsafe {
929 ptr::copy_nonoverlapping(data.as_ptr(), result, data.len());
930 *result.add(data.len()) = 0; }
932
933 result
934}
935
936unsafe fn parse_xml_document(filename: *const xmlChar) -> *mut _xmlDoc {
944 if filename.is_null() {
945 return ptr::null_mut();
946 }
947
948 let content = unsafe { io_read_file(filename) };
950 if content.is_null() {
951 return ptr::null_mut();
952 }
953
954 let content_bytes = unsafe { xmlstr_to_bytes(content) };
955 let size = content_bytes.len() as c_int;
956
957 let doc = unsafe {
959 crate::abi::exports_xml2::xmlReadMemory(
960 content as *const c_char,
961 size,
962 filename as *const c_char,
963 ptr::null(), 0, )
966 };
967
968 allocator::xmlFreeImpl(content as *mut c_void);
969
970 doc
971}
972
973unsafe fn find_root_element(doc: *mut _xmlDoc) -> *mut _xmlNode {
979 if doc.is_null() {
980 return ptr::null_mut();
981 }
982
983 let mut child = unsafe { (*doc).children };
984 while !child.is_null() {
985 let node_type = unsafe { (*child).type_ };
986 if node_type == XML_ELEMENT_NODE as c_int {
987 return child;
988 }
989 child = unsafe { (*child).next };
990 }
991
992 ptr::null_mut()
993}
994
995unsafe fn set_doc_recursive(node: *mut _xmlNode, doc: *mut _xmlDoc) {
1002 if node.is_null() {
1003 return;
1004 }
1005
1006 unsafe {
1007 (*node).doc = doc;
1008 }
1009
1010 let mut child = unsafe { (*node).children };
1012 while !child.is_null() {
1013 unsafe { set_doc_recursive(child, doc) };
1014 child = unsafe { (*child).next };
1015 }
1016
1017 let mut prop = unsafe { (*node).properties };
1019 while !prop.is_null() {
1020 unsafe {
1021 (*prop).doc = doc;
1022 if !(*prop).children.is_null() {
1023 set_doc_recursive((*prop).children, doc);
1024 }
1025 }
1026 prop = unsafe { (*prop).next };
1027 }
1028}
1029
1030unsafe fn check_namespace_declaration(node: *mut _xmlNode, ns_uri: *const xmlChar) -> bool {
1038 if node.is_null() {
1039 return false;
1040 }
1041
1042 let mut cur: *mut _xmlNode = node;
1043 while !cur.is_null() {
1044 let n = unsafe { &*cur };
1045 let mut ns_def = n.nsDef;
1046 while !ns_def.is_null() {
1047 let ns = unsafe { &*ns_def };
1048 if !ns.href.is_null() && unsafe { xml_str_equal(ns.href, ns_uri) } {
1049 return true;
1050 }
1051 ns_def = ns.next;
1052 }
1053 cur = n.parent;
1054 }
1055
1056 false
1057}
1058
1059unsafe fn xml_str_equal(a: *const xmlChar, b: *const xmlChar) -> bool {
1065 if a.is_null() && b.is_null() {
1066 return true;
1067 }
1068 if a.is_null() || b.is_null() {
1069 return false;
1070 }
1071 unsafe { crate::abi::exports_xml2::xmlStrEqual(a, b) != 0 }
1072}
1073
1074unsafe fn is_xinclude_ns_uri(href: *const xmlChar) -> bool {
1082 unsafe {
1083 xml_str_equal(href, XINCLUDE_NS.as_ptr() as *const xmlChar)
1084 || xml_str_equal(href, XINCLUDE_OLD_NS.as_ptr() as *const xmlChar)
1085 }
1086}
1087
1088#[cfg(test)]
1093mod tests {
1094 use super::*;
1095 use crate::abi::allocator;
1096
1097 use crate::xml::tree;
1098 use std::os::raw::{c_char, c_int};
1099
1100 #[allow(dead_code)]
1105 unsafe fn create_doc_from_xml(xml: &[u8]) -> *mut _xmlDoc {
1115 let doc = unsafe {
1116 crate::abi::exports_xml2::xmlReadMemory(
1117 xml.as_ptr() as *const c_char,
1118 xml.len() as c_int,
1119 ptr::null(),
1120 ptr::null(),
1121 0,
1122 )
1123 };
1124 if doc.is_null() {
1125 return ptr::null_mut();
1126 }
1127 doc
1128 }
1129
1130 unsafe fn create_simple_doc() -> *mut _xmlDoc {
1132 let doc = tree::new_doc(ptr::null());
1133 assert!(!doc.is_null(), "Failed to create doc");
1134
1135 let root = tree::new_child(
1136 doc as *mut _xmlNode,
1137 ptr::null_mut(),
1138 c"root".as_ptr() as *const xmlChar,
1139 );
1140 assert!(!root.is_null(), "Failed to create root");
1141
1142 doc
1143 }
1144
1145 unsafe fn create_ns(
1147 node: *mut _xmlNode,
1148 prefix: *const xmlChar,
1149 href: *const xmlChar,
1150 ) -> *mut _xmlNs {
1151 tree::new_ns(node, href, prefix)
1152 }
1153
1154 unsafe fn create_doc_with_xinclude_ns() -> (*mut _xmlDoc, *mut _xmlNode) {
1156 let doc = tree::new_doc(ptr::null());
1157 assert!(!doc.is_null());
1158 let root = tree::new_child(
1159 doc as *mut _xmlNode,
1160 ptr::null_mut(),
1161 c"root".as_ptr() as *const xmlChar,
1162 );
1163 assert!(!root.is_null());
1164 create_ns(
1165 root,
1166 c"xi".as_ptr() as *const xmlChar,
1167 XINCLUDE_NS.as_ptr() as *const xmlChar,
1168 );
1169 (doc, root)
1170 }
1171
1172 unsafe fn create_include_child(
1174 parent: *mut _xmlNode,
1175 href: Option<&[u8]>,
1176 parse: Option<&[u8]>,
1177 ) -> *mut _xmlNode {
1178 let ns = create_ns(
1179 parent,
1180 c"xi".as_ptr() as *const xmlChar,
1181 XINCLUDE_NS.as_ptr() as *const xmlChar,
1182 );
1183 let elem = tree::new_child(parent, ns, c"include".as_ptr() as *const xmlChar);
1184 if let Some(h) = href {
1185 let h_str = crate::xml::string::bytes_to_xmlstr(h);
1186 tree::set_prop(elem, ATTR_HREF.as_ptr() as *const xmlChar, h_str);
1187 allocator::xmlFreeImpl(h_str as *mut c_void);
1188 }
1189 if let Some(p) = parse {
1190 let p_str = crate::xml::string::bytes_to_xmlstr(p);
1191 tree::set_prop(elem, ATTR_PARSE.as_ptr() as *const xmlChar, p_str);
1192 allocator::xmlFreeImpl(p_str as *mut c_void);
1193 }
1194 elem
1195 }
1196
1197 unsafe fn create_fallback_child(parent: *mut _xmlNode) -> *mut _xmlNode {
1199 let ns = create_ns(
1200 parent,
1201 c"xi".as_ptr() as *const xmlChar,
1202 XINCLUDE_NS.as_ptr() as *const xmlChar,
1203 );
1204 tree::new_child(parent, ns, c"fallback".as_ptr() as *const xmlChar)
1205 }
1206 #[allow(dead_code)]
1207 unsafe fn find_element(doc: *mut _xmlDoc, name: *const xmlChar) -> *mut _xmlNode {
1216 if doc.is_null() {
1217 return ptr::null_mut();
1218 }
1219 let mut child = unsafe { (*doc).children };
1220 while !child.is_null() {
1221 let result = unsafe { find_element_recursive(child, name) };
1222 if !result.is_null() {
1223 return result;
1224 }
1225 child = unsafe { (*child).next };
1226 }
1227 ptr::null_mut()
1228 }
1229
1230 #[allow(dead_code)]
1231 unsafe fn find_element_recursive(node: *mut _xmlNode, name: *const xmlChar) -> *mut _xmlNode {
1240 if node.is_null() {
1241 return ptr::null_mut();
1242 }
1243 let n = unsafe { &*node };
1244 if n.type_ == XML_ELEMENT_NODE as c_int
1245 && !n.name.is_null()
1246 && unsafe { xml_str_equal(n.name, name) }
1247 {
1248 return node;
1249 }
1250 let mut child = n.children;
1251 while !child.is_null() {
1252 let result = unsafe { find_element_recursive(child, name) };
1253 if !result.is_null() {
1254 return result;
1255 }
1256 child = unsafe { (*child).next };
1257 }
1258 ptr::null_mut()
1259 }
1260
1261 unsafe fn count_elements(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
1270 if doc.is_null() {
1271 return 0;
1272 }
1273 let mut count: c_int = 0;
1274 let mut child = unsafe { (*doc).children };
1275 while !child.is_null() {
1276 count += unsafe { count_elements_recursive(child, name) };
1277 child = unsafe { (*child).next };
1278 }
1279 count
1280 }
1281
1282 unsafe fn count_elements_recursive(node: *mut _xmlNode, name: *const xmlChar) -> c_int {
1291 if node.is_null() {
1292 return 0;
1293 }
1294 let mut count: c_int = 0;
1295 let n = unsafe { &*node };
1296 if n.type_ == XML_ELEMENT_NODE as c_int
1297 && !n.name.is_null()
1298 && unsafe { xml_str_equal(n.name, name) }
1299 {
1300 count += 1;
1301 }
1302 let mut child = n.children;
1303 while !child.is_null() {
1304 count += unsafe { count_elements_recursive(child, name) };
1305 child = unsafe { (*child).next };
1306 }
1307 count
1308 }
1309
1310 #[test]
1315 fn test_is_xinclude_element() {
1324 unsafe {
1325 let doc = create_simple_doc();
1326 assert!(!doc.is_null());
1327 let root = (*doc).children;
1328 assert!(!root.is_null());
1329 assert!(!is_xinclude_element(root));
1330 assert!(!is_xinclude_element(ptr::null_mut()));
1331 tree::free_doc(doc);
1332 }
1333 }
1334
1335 #[test]
1336 fn test_xinclude_namespace_detection() {
1345 unsafe {
1346 let (doc, root) = create_doc_with_xinclude_ns();
1347 let include = create_include_child(root, Some(b"test.xml"), None);
1348 assert!(!include.is_null());
1349 assert!(is_xinclude_element(include), "Should detect xi:include");
1350
1351 let regular =
1353 tree::new_child(root, ptr::null_mut(), c"regular".as_ptr() as *const xmlChar);
1354 assert!(!regular.is_null());
1355 assert!(!is_xinclude_element(regular), "Regular elem not xinclude");
1356
1357 tree::free_doc(doc);
1358 }
1359 }
1360
1361 #[test]
1362 fn test_find_fallback_child() {
1371 unsafe {
1372 let (doc, root) = create_doc_with_xinclude_ns();
1373 let include = create_include_child(root, None, None);
1374 assert!(!include.is_null());
1375 let fallback = create_fallback_child(include);
1376 assert!(!fallback.is_null());
1377
1378 let found = find_fallback_child(include);
1379 assert!(!found.is_null(), "Should find fallback child");
1380
1381 let no_fallback = find_fallback_child(root);
1382 assert!(no_fallback.is_null(), "Root should not have fallback");
1383
1384 tree::free_doc(doc);
1385 }
1386 }
1387
1388 #[test]
1389 fn test_xml_str_equal() {
1398 unsafe {
1399 assert!(xml_str_equal(
1400 c"hello".as_ptr() as *const xmlChar,
1401 c"hello".as_ptr() as *const xmlChar,
1402 ));
1403 assert!(!xml_str_equal(
1404 c"hello".as_ptr() as *const xmlChar,
1405 c"world".as_ptr() as *const xmlChar,
1406 ));
1407 assert!(!xml_str_equal(
1408 ptr::null(),
1409 c"hello".as_ptr() as *const xmlChar
1410 ));
1411 assert!(!xml_str_equal(
1412 c"hello".as_ptr() as *const xmlChar,
1413 ptr::null()
1414 ));
1415 assert!(xml_str_equal(ptr::null(), ptr::null()));
1416 }
1417 }
1418
1419 #[test]
1420 fn test_xinclude_process_null_doc() {
1427 unsafe {
1428 assert_eq!(xinclude_process(ptr::null_mut()), XINCLUDE_FAILURE);
1429 }
1430 }
1431
1432 #[test]
1433 fn test_xinclude_process_no_includes() {
1441 unsafe {
1442 let doc = create_simple_doc();
1443 assert_eq!(xinclude_process(doc), 0);
1444 tree::free_doc(doc);
1445 }
1446 }
1447
1448 #[test]
1449 fn test_xinclude_process_with_includes() {
1458 unsafe {
1459 let (doc, root) = create_doc_with_xinclude_ns();
1461 create_include_child(root, Some(b"nonexistent.xml"), None);
1462 let result = xinclude_process(doc);
1463 assert!(result >= 0, "Should handle missing files: {}", result);
1464 tree::free_doc(doc);
1465 }
1466 }
1467
1468 #[test]
1469 fn test_xinclude_fallback_content() {
1478 unsafe {
1479 let (doc, root) = create_doc_with_xinclude_ns();
1480 let include = create_include_child(root, Some(b"nonexistent.xml"), None);
1481 let fb = create_fallback_child(include);
1482 let fb_child = tree::new_child(
1484 fb,
1485 ptr::null_mut(),
1486 c"fallback-elem".as_ptr() as *const xmlChar,
1487 );
1488 assert!(!fb_child.is_null());
1489
1490 let before = count_elements(doc, c"fallback-elem".as_ptr() as *const xmlChar);
1491 assert!(before > 0, "Should have fallback-elem before processing");
1492
1493 let result = xinclude_process(doc);
1494 assert!(result >= 0, "Should handle fallback: {}", result);
1495 tree::free_doc(doc);
1496 }
1497 }
1498
1499 #[test]
1500 fn test_xinclude_circular_reference_detection() {
1509 unsafe {
1510 let (doc, root) = create_doc_with_xinclude_ns();
1511 create_include_child(root, Some(b"self-ref.xml"), None);
1512 let result = xinclude_process(doc);
1513 assert!(result >= 0, "Circular ref should not crash: {}", result);
1514 tree::free_doc(doc);
1515 }
1516 }
1517
1518 #[test]
1519 fn test_xinclude_parse_attribute_detection() {
1528 unsafe {
1529 let (doc, root) = create_doc_with_xinclude_ns();
1530 create_include_child(root, Some(b"test.xml"), Some(b"xml"));
1531 create_include_child(root, Some(b"test.txt"), Some(b"text"));
1532 create_include_child(root, Some(b"default.xml"), None);
1533
1534 let mut count = 0;
1536 let mut child = (*root).children;
1537 while !child.is_null() {
1538 if is_xinclude_element(child) {
1539 count += 1;
1540 }
1541 child = (*child).next;
1542 }
1543 assert_eq!(count, 3, "Should have 3 include elements");
1544 tree::free_doc(doc);
1545 }
1546 }
1547
1548 #[test]
1549 fn test_xinclude_process_functions() {
1557 unsafe {
1558 let doc = create_simple_doc();
1559 let r1 = xinclude_process(doc);
1560 assert!(r1 >= 0);
1561 let r2 = xinclude_process_flags(doc, 0);
1562 assert!(r2 >= 0);
1563 tree::free_doc(doc);
1564 }
1565 }
1566
1567 #[test]
1568 fn test_xinclude_process_with_empty_href() {
1576 unsafe {
1577 let (doc, root) = create_doc_with_xinclude_ns();
1578 let include = create_include_child(root, None, None);
1579 create_fallback_child(include);
1580 let result = xinclude_process(doc);
1581 assert!(result >= 0, "Empty href with fallback: {}", result);
1582 tree::free_doc(doc);
1583 }
1584 }
1585
1586 #[test]
1587 fn test_set_doc_recursive() {
1597 unsafe {
1598 let doc = tree::new_doc(ptr::null());
1599 assert!(!doc.is_null());
1600 let parent = tree::new_child(
1601 doc as *mut _xmlNode,
1602 ptr::null_mut(),
1603 c"parent".as_ptr() as *const xmlChar,
1604 );
1605 assert!(!parent.is_null());
1606 let detached = tree::new_node(ptr::null_mut(), c"detached".as_ptr() as *const xmlChar);
1607 assert!(!detached.is_null());
1608 assert!((*detached).doc.is_null());
1609 set_doc_recursive(detached, doc);
1610 assert_eq!((*detached).doc, doc);
1611 tree::free_node(detached);
1612 tree::free_doc(doc);
1613 }
1614 }
1615
1616 #[test]
1617 fn test_find_root_element() {
1625 unsafe {
1626 let doc = create_simple_doc();
1627 let root = find_root_element(doc);
1628 assert!(!root.is_null());
1629 assert_eq!((*root).type_, XML_ELEMENT_NODE as c_int);
1630 tree::free_doc(doc);
1631 }
1632 }
1633
1634 #[test]
1635 fn test_xinclude_xpointer_attribute() {
1645 unsafe {
1646 let (doc, root) = create_doc_with_xinclude_ns();
1647 let include = create_include_child(root, Some(b"test.xml"), None);
1648 let xptr_val = crate::xml::string::bytes_to_xmlstr(b"xpointer(//target)");
1649 tree::set_prop(include, ATTR_XPOINTER.as_ptr() as *const xmlChar, xptr_val);
1650 allocator::xmlFreeImpl(xptr_val as *mut c_void);
1651
1652 let xptr = tree::get_prop(include, ATTR_XPOINTER.as_ptr() as *const xmlChar);
1653 assert!(!xptr.is_null(), "Should have xpointer attribute");
1654 assert_eq!(xmlstr_to_bytes(xptr), b"xpointer(//target)");
1655 allocator::xmlFreeImpl(xptr as *mut c_void);
1656
1657 tree::free_doc(doc);
1658 }
1659 }
1660
1661 #[test]
1662 fn test_xinclude_accept_attributes() {
1671 unsafe {
1672 let (doc, root) = create_doc_with_xinclude_ns();
1673 let include = create_include_child(root, Some(b"data.xml"), None);
1674
1675 let accept_val = crate::xml::string::bytes_to_xmlstr(b"application/xml");
1676 tree::set_prop(include, ATTR_ACCEPT.as_ptr() as *const xmlChar, accept_val);
1677 allocator::xmlFreeImpl(accept_val as *mut c_void);
1678
1679 let lang_val = crate::xml::string::bytes_to_xmlstr(b"en");
1680 tree::set_prop(
1681 include,
1682 ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar,
1683 lang_val,
1684 );
1685 allocator::xmlFreeImpl(lang_val as *mut c_void);
1686
1687 let accept = tree::get_prop(include, ATTR_ACCEPT.as_ptr() as *const xmlChar);
1688 assert!(!accept.is_null());
1689 assert_eq!(xmlstr_to_bytes(accept), b"application/xml");
1690 allocator::xmlFreeImpl(accept as *mut c_void);
1691
1692 let lang = tree::get_prop(include, ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar);
1693 assert!(!lang.is_null());
1694 assert_eq!(xmlstr_to_bytes(lang), b"en");
1695 allocator::xmlFreeImpl(lang as *mut c_void);
1696
1697 tree::free_doc(doc);
1698 }
1699 }
1700
1701 #[test]
1702 fn test_xinclude_encoding_attribute() {
1710 unsafe {
1711 let (doc, root) = create_doc_with_xinclude_ns();
1712 let include = create_include_child(root, Some(b"data.txt"), Some(b"text"));
1713
1714 let enc_val = crate::xml::string::bytes_to_xmlstr(b"UTF-8");
1715 tree::set_prop(include, ATTR_ENCODING.as_ptr() as *const xmlChar, enc_val);
1716 allocator::xmlFreeImpl(enc_val as *mut c_void);
1717
1718 let encoding = tree::get_prop(include, ATTR_ENCODING.as_ptr() as *const xmlChar);
1719 assert!(!encoding.is_null());
1720 assert_eq!(xmlstr_to_bytes(encoding), b"UTF-8");
1721 allocator::xmlFreeImpl(encoding as *mut c_void);
1722
1723 tree::free_doc(doc);
1724 }
1725 }
1726
1727 #[test]
1728 fn test_xinclude_process_flags_equivalence() {
1736 unsafe {
1737 let doc = create_simple_doc();
1738 let r1 = xinclude_process(doc);
1739 let r2 = xinclude_process_flags(doc, 0);
1740 assert_eq!(r1, r2);
1741 tree::free_doc(doc);
1742 }
1743 }
1744
1745 #[test]
1746 fn test_xinclude_process_flags_noxincnode() {
1753 unsafe {
1754 let doc = create_simple_doc();
1755 let result = xinclude_process_flags(doc, XML_PARSE_NOXINCNODE);
1756 assert_eq!(result, 0);
1757 tree::free_doc(doc);
1758 }
1759 }
1760
1761 #[test]
1762 #[ignore = "pre-existing tree module cleanup bug with modified trees"]
1763 fn test_complex_nested_includes_structure() {
1774 unsafe {
1775 let doc = tree::new_doc(ptr::null());
1777 assert!(!doc.is_null());
1778 let root = tree::new_child(
1779 doc as *mut _xmlNode,
1780 ptr::null_mut(),
1781 c"root".as_ptr() as *const xmlChar,
1782 );
1783 assert!(!root.is_null());
1784 create_ns(
1785 root,
1786 c"xi".as_ptr() as *const xmlChar,
1787 XINCLUDE_NS.as_ptr() as *const xmlChar,
1788 );
1789
1790 create_include_child(root, Some(b"nonexistent1.xml"), None);
1791
1792 let inc2 = create_include_child(root, Some(b"nonexistent2.xml"), None);
1793 let fb2 = create_fallback_child(inc2);
1794 tree::new_child(
1795 fb2,
1796 ptr::null_mut(),
1797 c"fallback-content".as_ptr() as *const xmlChar,
1798 );
1799
1800 create_include_child(root, Some(b"nonexistent3.txt"), Some(b"text"));
1801
1802 let result = xinclude_process(doc);
1803 assert!(result >= 0, "Complex structure: {}", result);
1804 }
1805 }
1806
1807 #[test]
1808 fn test_xinclude_process_xml_memory_cleanup() {
1816 unsafe {
1817 let doc = create_simple_doc();
1818 assert!(xinclude_process(doc) >= 0);
1819 tree::free_doc(doc);
1820 }
1821 }
1822
1823 #[test]
1824 fn test_mark_doc_xinclude_processed() {
1832 unsafe {
1833 let doc = create_simple_doc();
1834 assert_eq!((*doc).properties & XML_DOC_XINCLUDE as c_int, 0);
1835 mark_doc_xinclude_processed(doc);
1836 assert_ne!((*doc).properties & XML_DOC_XINCLUDE as c_int, 0);
1837 tree::free_doc(doc);
1838 }
1839 }
1840
1841 #[test]
1842 fn test_xinclude_xinclude_start_end_nodes() {
1853 unsafe {
1854 let doc = create_simple_doc();
1855 let root = find_root_element(doc);
1856 assert!(!root.is_null());
1857
1858 let sentinel =
1860 tree::new_node(ptr::null_mut(), c"XIncludeStart".as_ptr() as *const xmlChar);
1861 assert!(!sentinel.is_null());
1862 (*sentinel).type_ = XML_XINCLUDE_START as c_int;
1863 (*sentinel).doc = doc;
1864 let first_child = (*root).children;
1866 if !first_child.is_null() {
1867 (*sentinel).parent = root;
1869 (*sentinel).prev = first_child;
1870 (*sentinel).next = (*first_child).next;
1871 if !(*first_child).next.is_null() {
1872 (*(*first_child).next).prev = sentinel;
1873 }
1874 (*first_child).next = sentinel;
1875 if (*root).last == first_child {
1876 (*root).last = sentinel;
1877 }
1878 }
1879
1880 let mut visited = Vec::new();
1881 let count = { process_node_tree(root, doc, &mut visited) };
1882 assert_eq!(count, 0, "Should not process sentinel nodes");
1883
1884 if !(*sentinel).prev.is_null() {
1886 (*(*sentinel).prev).next = (*sentinel).next;
1887 }
1888 if !(*sentinel).next.is_null() {
1889 (*(*sentinel).next).prev = (*sentinel).prev;
1890 }
1891 (*sentinel).prev = ptr::null_mut();
1892 (*sentinel).next = ptr::null_mut();
1893 (*sentinel).parent = ptr::null_mut();
1894
1895 tree::free_node(sentinel);
1896 tree::free_doc(doc);
1897 }
1898 }
1899}