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 if flags & XML_PARSE_NOXINCNODE != 0 {
200 return XINCLUDE_NO_NODES;
201 }
202
203 let mut visited: Vec<Vec<u8>> = Vec::new();
205
206 let count = unsafe { process_doc(doc, &mut visited) };
207
208 if count > 0 {
209 unsafe { mark_doc_xinclude_processed(doc) };
210 }
211
212 count
213}
214
215unsafe fn mark_doc_xinclude_processed(doc: *mut _xmlDoc) {
225 unsafe {
226 let d = &mut *doc;
227 d.properties |= XML_DOC_XINCLUDE as c_int;
228 }
229}
230
231unsafe fn process_doc(doc: *mut _xmlDoc, visited: &mut Vec<Vec<u8>>) -> c_int {
238 let mut count: c_int = 0;
239
240 let root = unsafe { find_root_element(doc) };
242 if root.is_null() {
243 return XINCLUDE_NO_NODES;
244 }
245
246 unsafe {
248 count += process_node_tree(root, doc, visited);
249 }
250
251 count
252}
253
254unsafe fn process_node_tree(
262 node: *mut _xmlNode,
263 doc: *mut _xmlDoc,
264 visited: &mut Vec<Vec<u8>>,
265) -> c_int {
266 if node.is_null() {
267 return 0;
268 }
269
270 let mut count: c_int = 0;
271
272 let node_type = unsafe { (*node).type_ };
277 if node_type == XML_XINCLUDE_START as c_int || node_type == XML_XINCLUDE_END as c_int {
278 if node_type == XML_XINCLUDE_START as c_int {
281 let mut child = unsafe { (*node).children };
282 while !child.is_null() {
283 count += unsafe { process_node_tree(child, doc, visited) };
284 child = unsafe { (*child).next };
285 }
286 }
287 return count;
288 }
289
290 let mut children: Vec<*mut _xmlNode> = Vec::new();
293 let mut child = unsafe { (*node).children };
294 while !child.is_null() {
295 children.push(child);
296 child = unsafe { (*child).next };
297 }
298
299 for child_node in children {
300 if unsafe { is_xinclude_element(child_node) } {
302 let processed = unsafe { process_single_include(child_node, doc, visited) };
303 if processed >= 0 {
304 count += processed;
305 } else {
306 count = -1; }
308 } else {
309 let child_type = unsafe { (*child_node).type_ };
311 if child_type == XML_ELEMENT_NODE as c_int
312 || child_type == XML_DOCUMENT_NODE as c_int
313 || child_type == XML_DOCUMENT_FRAG_NODE as c_int
314 || child_type == XML_XINCLUDE_START as c_int
315 {
316 count += unsafe { process_node_tree(child_node, doc, visited) };
317 }
318 }
319 }
320
321 count
322}
323
324unsafe fn is_xinclude_element(node: *mut _xmlNode) -> bool {
334 if node.is_null() {
335 return false;
336 }
337
338 let n = unsafe { &*node };
339 if n.type_ != XML_ELEMENT_NODE as c_int {
340 return false;
341 }
342
343 if n.name.is_null() {
344 return false;
345 }
346
347 let has_xinclude_ns = if !n.ns.is_null() {
349 let ns = unsafe { &*n.ns };
350 !ns.href.is_null() && unsafe { is_xinclude_ns_uri(ns.href) }
351 } else {
352 check_namespace_declaration(node, XINCLUDE_NS.as_ptr() as *const xmlChar)
356 || check_namespace_declaration(node, XINCLUDE_OLD_NS.as_ptr() as *const xmlChar)
357 };
358
359 if !has_xinclude_ns {
360 return false;
361 }
362
363 let name_bytes = unsafe { xmlstr_to_bytes(n.name) };
365 let local_name = if let Some(pos) = name_bytes.iter().position(|&b| b == b':') {
366 &name_bytes[pos + 1..]
367 } else {
368 name_bytes
369 };
370
371 local_name == b"include"
372}
373
374unsafe fn is_fallback_element(node: *mut _xmlNode) -> bool {
380 if node.is_null() {
381 return false;
382 }
383
384 let n = unsafe { &*node };
385 if n.type_ != XML_ELEMENT_NODE as c_int {
386 return false;
387 }
388
389 if n.name.is_null() {
390 return false;
391 }
392
393 let has_xinclude_ns = if !n.ns.is_null() {
395 let ns = unsafe { &*n.ns };
396 !ns.href.is_null() && unsafe { is_xinclude_ns_uri(ns.href) }
397 } else {
398 check_namespace_declaration(node, XINCLUDE_NS.as_ptr() as *const xmlChar)
399 || check_namespace_declaration(node, XINCLUDE_OLD_NS.as_ptr() as *const xmlChar)
400 };
401
402 if !has_xinclude_ns {
403 return false;
404 }
405
406 let name_bytes = unsafe { xmlstr_to_bytes(n.name) };
408 let local_name = if let Some(pos) = name_bytes.iter().position(|&b| b == b':') {
409 &name_bytes[pos + 1..]
410 } else {
411 name_bytes
412 };
413
414 local_name == b"fallback"
415}
416
417unsafe fn process_single_include(
425 include_node: *mut _xmlNode,
426 doc: *mut _xmlDoc,
427 visited: &mut Vec<Vec<u8>>,
428) -> c_int {
429 let href = unsafe { tree::get_prop(include_node, ATTR_HREF.as_ptr() as *const xmlChar) };
431
432 if href.is_null() {
434 return unsafe { apply_fallback(include_node, doc, visited) };
435 }
436
437 let href_str = unsafe { xmlstr_to_bytes(href) };
438
439 if visited.iter().any(|v| v.as_slice() == href_str) {
441 allocator::xmlFreeImpl(href as *mut c_void);
442 return unsafe { apply_fallback(include_node, doc, visited) };
443 }
444
445 let parse_attr = unsafe { tree::get_prop(include_node, ATTR_PARSE.as_ptr() as *const xmlChar) };
447 let is_text_mode = if !parse_attr.is_null() {
448 let parse_str = unsafe { xmlstr_to_bytes(parse_attr) };
449 let result = parse_str == b"text";
450 allocator::xmlFreeImpl(parse_attr as *mut c_void);
451 result
452 } else {
453 false
454 };
455
456 let xpointer_attr =
458 unsafe { tree::get_prop(include_node, ATTR_XPOINTER.as_ptr() as *const xmlChar) };
459
460 let accept_attr =
462 unsafe { tree::get_prop(include_node, ATTR_ACCEPT.as_ptr() as *const xmlChar) };
463
464 let accept_language_attr = unsafe {
466 tree::get_prop(
467 include_node,
468 ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar,
469 )
470 };
471
472 visited.push(href_str.to_vec());
474
475 let result = if is_text_mode {
476 unsafe { process_text_include(include_node, doc, href, accept_attr, visited) }
477 } else {
478 unsafe { process_xml_include(include_node, doc, href, xpointer_attr, visited) }
479 };
480
481 visited.pop();
483
484 allocator::xmlFreeImpl(href as *mut c_void);
486
487 if !parse_attr.is_null() {
488 allocator::xmlFreeImpl(parse_attr as *mut c_void);
489 }
490 if !xpointer_attr.is_null() {
491 allocator::xmlFreeImpl(xpointer_attr as *mut c_void);
492 }
493 if !accept_attr.is_null() {
494 allocator::xmlFreeImpl(accept_attr as *mut c_void);
495 }
496 if !accept_language_attr.is_null() {
497 allocator::xmlFreeImpl(accept_language_attr as *mut c_void);
498 }
499
500 match result {
501 Ok(processed) => processed,
502 Err(()) => unsafe { apply_fallback(include_node, doc, visited) },
503 }
504}
505
506unsafe fn process_text_include(
515 include_node: *mut _xmlNode,
516 doc: *mut _xmlDoc,
517 href: *mut xmlChar,
518 _accept: *mut xmlChar,
519 _visited: &mut Vec<Vec<u8>>,
520) -> Result<c_int, ()> {
521 let content = unsafe { io_read_file(href) };
523
524 if content.is_null() {
525 return Err(());
526 }
527
528 let encoding_attr =
530 unsafe { tree::get_prop(include_node, ATTR_ENCODING.as_ptr() as *const xmlChar) };
531
532 let text_node = unsafe { tree::new_text(content as *const xmlChar) };
534 if text_node.is_null() {
535 allocator::xmlFreeImpl(content as *mut c_void);
536 if !encoding_attr.is_null() {
537 allocator::xmlFreeImpl(encoding_attr as *mut c_void);
538 }
539 return Err(());
540 }
541
542 unsafe { replace_node_with_content(include_node, text_node, doc) };
544
545 allocator::xmlFreeImpl(content as *mut c_void);
546 if !encoding_attr.is_null() {
547 allocator::xmlFreeImpl(encoding_attr as *mut c_void);
548 }
549
550 Ok(1)
551}
552
553unsafe fn process_xml_include(
562 include_node: *mut _xmlNode,
563 doc: *mut _xmlDoc,
564 href: *mut xmlChar,
565 xpointer_attr: *mut xmlChar,
566 visited: &mut Vec<Vec<u8>>,
567) -> Result<c_int, ()> {
568 let included_doc = unsafe { parse_xml_document(href) };
570 if included_doc.is_null() {
571 return Err(());
572 }
573
574 let result = if !xpointer_attr.is_null() {
575 let xptr_str = unsafe { xmlstr_to_bytes(xpointer_attr) };
577 let xptr_utf8 = unsafe { std::str::from_utf8_unchecked(xptr_str) };
578 unsafe { include_via_xpointer(include_node, doc, included_doc, xptr_utf8, visited) }
579 } else {
580 unsafe { include_document_element(include_node, doc, included_doc, visited) }
582 };
583
584 let _ = unsafe { process_doc(included_doc, visited) };
586
587 unsafe { tree::free_doc(included_doc) };
590
591 result
592}
593
594unsafe fn include_document_element(
600 include_node: *mut _xmlNode,
601 doc: *mut _xmlDoc,
602 included_doc: *mut _xmlDoc,
603 _visited: &mut Vec<Vec<u8>>,
604) -> Result<c_int, ()> {
605 let root = unsafe { find_root_element(included_doc) };
606 if root.is_null() {
607 return Err(());
608 }
609
610 let copy = unsafe { tree::copy_node(root, 1) };
612 if copy.is_null() {
613 return Err(());
614 }
615
616 unsafe { set_doc_recursive(copy, doc) };
618
619 unsafe { replace_node_with_content(include_node, copy, doc) };
621
622 Ok(1)
623}
624
625unsafe fn include_via_xpointer(
631 include_node: *mut _xmlNode,
632 doc: *mut _xmlDoc,
633 included_doc: *mut _xmlDoc,
634 xpointer_expr: &str,
635 _visited: &mut Vec<Vec<u8>>,
636) -> Result<c_int, ()> {
637 let target = unsafe { xpointer::xptr_eval(xpointer_expr, included_doc) };
639
640 match target {
641 Some(target_node) => {
642 let copy = unsafe { tree::copy_node(target_node, 1) };
644 if copy.is_null() {
645 return Err(());
646 }
647
648 unsafe { set_doc_recursive(copy, doc) };
650
651 unsafe { replace_node_with_content(include_node, copy, doc) };
653
654 Ok(1)
655 }
656 None => Err(()),
657 }
658}
659
660unsafe fn apply_fallback(
668 include_node: *mut _xmlNode,
669 doc: *mut _xmlDoc,
670 visited: &mut Vec<Vec<u8>>,
671) -> c_int {
672 if include_node.is_null() {
673 return XINCLUDE_FAILURE;
674 }
675
676 let fallback = unsafe { find_fallback_child(include_node) };
678 if fallback.is_null() {
679 return 0; }
681
682 let mut fallback_children: Vec<*mut _xmlNode> = Vec::new();
684 let mut child = unsafe { (*fallback).children };
685 while !child.is_null() {
686 let next = unsafe { (*child).next };
687 fallback_children.push(child);
688 child = next;
689 }
690
691 if fallback_children.is_empty() {
692 unsafe { remove_node(include_node) };
694 return 1;
695 }
696
697 let parent = unsafe { (*include_node).parent };
699 if parent.is_null() {
700 return XINCLUDE_FAILURE;
701 }
702
703 let mut first_inserted: *mut _xmlNode = ptr::null_mut();
704 let mut last_inserted: *mut _xmlNode = ptr::null_mut();
705
706 for fb_child in &fallback_children {
707 let copy = unsafe { tree::copy_node(*fb_child, 1) };
708 if copy.is_null() {
709 continue;
710 }
711 unsafe { set_doc_recursive(copy, doc) };
712
713 unsafe {
715 let inserted = tree::add_sibling_before(include_node, copy);
716 if !inserted.is_null() {
717 if first_inserted.is_null() {
718 first_inserted = inserted;
719 }
720 last_inserted = inserted;
721 }
722 }
723 }
724
725 if !first_inserted.is_null() {
727 let mut cur = first_inserted;
728 loop {
729 unsafe {
730 let _ = process_node_tree(cur, doc, visited);
731 }
732 if cur == last_inserted {
733 break;
734 }
735 cur = unsafe { (*cur).next };
736 if cur.is_null() {
737 break;
738 }
739 }
740 }
741
742 unsafe { remove_node(include_node) };
744
745 1
746}
747
748unsafe fn find_fallback_child(node: *mut _xmlNode) -> *mut _xmlNode {
754 if node.is_null() {
755 return ptr::null_mut();
756 }
757
758 let mut child = unsafe { (*node).children };
759 while !child.is_null() {
760 if unsafe { is_fallback_element(child) } {
761 return child;
762 }
763 child = unsafe { (*child).next };
764 }
765
766 ptr::null_mut()
767}
768
769unsafe fn replace_node_with_content(
775 old_node: *mut _xmlNode,
776 new_content: *mut _xmlNode,
777 _doc: *mut _xmlDoc,
778) {
779 if old_node.is_null() || new_content.is_null() {
780 return;
781 }
782
783 let parent = unsafe { (*old_node).parent };
784 if parent.is_null() {
785 unsafe {
788 tree::add_sibling(old_node, new_content);
789 tree::unlink_node(old_node);
790 tree::free_node(old_node);
791 }
792 return;
793 }
794
795 unsafe {
797 tree::add_sibling_before(old_node, new_content);
798 tree::unlink_node(old_node);
799 tree::free_node(old_node);
800 }
801}
802
803unsafe fn remove_node(node: *mut _xmlNode) {
809 if node.is_null() {
810 return;
811 }
812 unsafe {
813 tree::unlink_node(node);
814 tree::free_node(node);
815 }
816}
817
818unsafe fn io_read_file(filename: *const xmlChar) -> *mut xmlChar {
826 if filename.is_null() {
827 return ptr::null_mut();
828 }
829
830 let c_filename = match std::ffi::CString::new(unsafe { xmlstr_to_bytes(filename) }) {
832 Ok(s) => s,
833 Err(_) => return ptr::null_mut(),
834 };
835
836 if let Some(data) =
841 crate::abi::exports_parser::read_uri_via_input_callbacks(c_filename.as_ptr())
842 {
843 if data.is_empty() {
844 return ptr::null_mut();
845 }
846 let result = unsafe { allocator::xmlMallocImpl(data.len() + 1) as *mut xmlChar };
847 if result.is_null() {
848 return ptr::null_mut();
849 }
850 unsafe {
851 ptr::copy_nonoverlapping(data.as_ptr(), result, data.len());
852 *result.add(data.len()) = 0; }
854 return result;
855 }
856
857 let fd = unsafe { libc::open(c_filename.as_ptr(), libc::O_RDONLY) };
858 if fd < 0 {
859 return ptr::null_mut();
860 }
861
862 let mut data = Vec::new();
863 let mut buf = [0u8; 4096];
864
865 loop {
866 let ret = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut c_void, buf.len()) };
867 if ret < 0 {
868 unsafe { libc::close(fd) };
869 return ptr::null_mut();
870 }
871 if ret == 0 {
872 break;
873 }
874 data.extend_from_slice(&buf[..ret as usize]);
875 }
876
877 unsafe { libc::close(fd) };
878
879 if data.is_empty() {
880 return ptr::null_mut();
881 }
882
883 let result = unsafe { allocator::xmlMallocImpl(data.len() + 1) as *mut xmlChar };
885 if result.is_null() {
886 return ptr::null_mut();
887 }
888
889 unsafe {
890 ptr::copy_nonoverlapping(data.as_ptr(), result, data.len());
891 *result.add(data.len()) = 0; }
893
894 result
895}
896
897unsafe fn parse_xml_document(filename: *const xmlChar) -> *mut _xmlDoc {
905 if filename.is_null() {
906 return ptr::null_mut();
907 }
908
909 let content = unsafe { io_read_file(filename) };
911 if content.is_null() {
912 return ptr::null_mut();
913 }
914
915 let content_bytes = unsafe { xmlstr_to_bytes(content) };
916 let size = content_bytes.len() as c_int;
917
918 let doc = unsafe {
920 crate::abi::exports_xml2::xmlReadMemory(
921 content as *const c_char,
922 size,
923 filename as *const c_char,
924 ptr::null(), 0, )
927 };
928
929 allocator::xmlFreeImpl(content as *mut c_void);
930
931 doc
932}
933
934unsafe fn find_root_element(doc: *mut _xmlDoc) -> *mut _xmlNode {
940 if doc.is_null() {
941 return ptr::null_mut();
942 }
943
944 let mut child = unsafe { (*doc).children };
945 while !child.is_null() {
946 let node_type = unsafe { (*child).type_ };
947 if node_type == XML_ELEMENT_NODE as c_int {
948 return child;
949 }
950 child = unsafe { (*child).next };
951 }
952
953 ptr::null_mut()
954}
955
956unsafe fn set_doc_recursive(node: *mut _xmlNode, doc: *mut _xmlDoc) {
963 if node.is_null() {
964 return;
965 }
966
967 unsafe {
968 (*node).doc = doc;
969 }
970
971 let mut child = unsafe { (*node).children };
973 while !child.is_null() {
974 unsafe { set_doc_recursive(child, doc) };
975 child = unsafe { (*child).next };
976 }
977
978 let mut prop = unsafe { (*node).properties };
980 while !prop.is_null() {
981 unsafe {
982 (*prop).doc = doc;
983 if !(*prop).children.is_null() {
984 set_doc_recursive((*prop).children, doc);
985 }
986 }
987 prop = unsafe { (*prop).next };
988 }
989}
990
991unsafe fn check_namespace_declaration(node: *mut _xmlNode, ns_uri: *const xmlChar) -> bool {
999 if node.is_null() {
1000 return false;
1001 }
1002
1003 let mut cur: *mut _xmlNode = node;
1004 while !cur.is_null() {
1005 let n = unsafe { &*cur };
1006 let mut ns_def = n.nsDef;
1007 while !ns_def.is_null() {
1008 let ns = unsafe { &*ns_def };
1009 if !ns.href.is_null() && unsafe { xml_str_equal(ns.href, ns_uri) } {
1010 return true;
1011 }
1012 ns_def = ns.next;
1013 }
1014 cur = n.parent;
1015 }
1016
1017 false
1018}
1019
1020unsafe fn xml_str_equal(a: *const xmlChar, b: *const xmlChar) -> bool {
1026 if a.is_null() && b.is_null() {
1027 return true;
1028 }
1029 if a.is_null() || b.is_null() {
1030 return false;
1031 }
1032 unsafe { crate::abi::exports_xml2::xmlStrEqual(a, b) != 0 }
1033}
1034
1035unsafe fn is_xinclude_ns_uri(href: *const xmlChar) -> bool {
1043 unsafe {
1044 xml_str_equal(href, XINCLUDE_NS.as_ptr() as *const xmlChar)
1045 || xml_str_equal(href, XINCLUDE_OLD_NS.as_ptr() as *const xmlChar)
1046 }
1047}
1048
1049#[cfg(test)]
1054mod tests {
1055 use super::*;
1056 use crate::abi::allocator;
1057
1058 use crate::xml::tree;
1059 use std::os::raw::{c_char, c_int};
1060
1061 #[allow(dead_code)]
1066 unsafe fn create_doc_from_xml(xml: &[u8]) -> *mut _xmlDoc {
1076 let doc = unsafe {
1077 crate::abi::exports_xml2::xmlReadMemory(
1078 xml.as_ptr() as *const c_char,
1079 xml.len() as c_int,
1080 ptr::null(),
1081 ptr::null(),
1082 0,
1083 )
1084 };
1085 if doc.is_null() {
1086 return ptr::null_mut();
1087 }
1088 doc
1089 }
1090
1091 unsafe fn create_simple_doc() -> *mut _xmlDoc {
1093 let doc = tree::new_doc(ptr::null());
1094 assert!(!doc.is_null(), "Failed to create doc");
1095
1096 let root = tree::new_child(
1097 doc as *mut _xmlNode,
1098 ptr::null_mut(),
1099 c"root".as_ptr() as *const xmlChar,
1100 );
1101 assert!(!root.is_null(), "Failed to create root");
1102
1103 doc
1104 }
1105
1106 unsafe fn create_ns(
1108 node: *mut _xmlNode,
1109 prefix: *const xmlChar,
1110 href: *const xmlChar,
1111 ) -> *mut _xmlNs {
1112 tree::new_ns(node, href, prefix)
1113 }
1114
1115 unsafe fn create_doc_with_xinclude_ns() -> (*mut _xmlDoc, *mut _xmlNode) {
1117 let doc = tree::new_doc(ptr::null());
1118 assert!(!doc.is_null());
1119 let root = tree::new_child(
1120 doc as *mut _xmlNode,
1121 ptr::null_mut(),
1122 c"root".as_ptr() as *const xmlChar,
1123 );
1124 assert!(!root.is_null());
1125 create_ns(
1126 root,
1127 c"xi".as_ptr() as *const xmlChar,
1128 XINCLUDE_NS.as_ptr() as *const xmlChar,
1129 );
1130 (doc, root)
1131 }
1132
1133 unsafe fn create_include_child(
1135 parent: *mut _xmlNode,
1136 href: Option<&[u8]>,
1137 parse: Option<&[u8]>,
1138 ) -> *mut _xmlNode {
1139 let ns = create_ns(
1140 parent,
1141 c"xi".as_ptr() as *const xmlChar,
1142 XINCLUDE_NS.as_ptr() as *const xmlChar,
1143 );
1144 let elem = tree::new_child(parent, ns, c"include".as_ptr() as *const xmlChar);
1145 if let Some(h) = href {
1146 let h_str = crate::xml::string::bytes_to_xmlstr(h);
1147 tree::set_prop(elem, ATTR_HREF.as_ptr() as *const xmlChar, h_str);
1148 allocator::xmlFreeImpl(h_str as *mut c_void);
1149 }
1150 if let Some(p) = parse {
1151 let p_str = crate::xml::string::bytes_to_xmlstr(p);
1152 tree::set_prop(elem, ATTR_PARSE.as_ptr() as *const xmlChar, p_str);
1153 allocator::xmlFreeImpl(p_str as *mut c_void);
1154 }
1155 elem
1156 }
1157
1158 unsafe fn create_fallback_child(parent: *mut _xmlNode) -> *mut _xmlNode {
1160 let ns = create_ns(
1161 parent,
1162 c"xi".as_ptr() as *const xmlChar,
1163 XINCLUDE_NS.as_ptr() as *const xmlChar,
1164 );
1165 tree::new_child(parent, ns, c"fallback".as_ptr() as *const xmlChar)
1166 }
1167 #[allow(dead_code)]
1168 unsafe fn find_element(doc: *mut _xmlDoc, name: *const xmlChar) -> *mut _xmlNode {
1177 if doc.is_null() {
1178 return ptr::null_mut();
1179 }
1180 let mut child = unsafe { (*doc).children };
1181 while !child.is_null() {
1182 let result = unsafe { find_element_recursive(child, name) };
1183 if !result.is_null() {
1184 return result;
1185 }
1186 child = unsafe { (*child).next };
1187 }
1188 ptr::null_mut()
1189 }
1190
1191 #[allow(dead_code)]
1192 unsafe fn find_element_recursive(node: *mut _xmlNode, name: *const xmlChar) -> *mut _xmlNode {
1201 if node.is_null() {
1202 return ptr::null_mut();
1203 }
1204 let n = unsafe { &*node };
1205 if n.type_ == XML_ELEMENT_NODE as c_int
1206 && !n.name.is_null()
1207 && unsafe { xml_str_equal(n.name, name) }
1208 {
1209 return node;
1210 }
1211 let mut child = n.children;
1212 while !child.is_null() {
1213 let result = unsafe { find_element_recursive(child, name) };
1214 if !result.is_null() {
1215 return result;
1216 }
1217 child = unsafe { (*child).next };
1218 }
1219 ptr::null_mut()
1220 }
1221
1222 unsafe fn count_elements(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
1231 if doc.is_null() {
1232 return 0;
1233 }
1234 let mut count: c_int = 0;
1235 let mut child = unsafe { (*doc).children };
1236 while !child.is_null() {
1237 count += unsafe { count_elements_recursive(child, name) };
1238 child = unsafe { (*child).next };
1239 }
1240 count
1241 }
1242
1243 unsafe fn count_elements_recursive(node: *mut _xmlNode, name: *const xmlChar) -> c_int {
1252 if node.is_null() {
1253 return 0;
1254 }
1255 let mut count: c_int = 0;
1256 let n = unsafe { &*node };
1257 if n.type_ == XML_ELEMENT_NODE as c_int
1258 && !n.name.is_null()
1259 && unsafe { xml_str_equal(n.name, name) }
1260 {
1261 count += 1;
1262 }
1263 let mut child = n.children;
1264 while !child.is_null() {
1265 count += unsafe { count_elements_recursive(child, name) };
1266 child = unsafe { (*child).next };
1267 }
1268 count
1269 }
1270
1271 #[test]
1276 fn test_is_xinclude_element() {
1285 unsafe {
1286 let doc = create_simple_doc();
1287 assert!(!doc.is_null());
1288 let root = (*doc).children;
1289 assert!(!root.is_null());
1290 assert!(!is_xinclude_element(root));
1291 assert!(!is_xinclude_element(ptr::null_mut()));
1292 tree::free_doc(doc);
1293 }
1294 }
1295
1296 #[test]
1297 fn test_xinclude_namespace_detection() {
1306 unsafe {
1307 let (doc, root) = create_doc_with_xinclude_ns();
1308 let include = create_include_child(root, Some(b"test.xml"), None);
1309 assert!(!include.is_null());
1310 assert!(is_xinclude_element(include), "Should detect xi:include");
1311
1312 let regular =
1314 tree::new_child(root, ptr::null_mut(), c"regular".as_ptr() as *const xmlChar);
1315 assert!(!regular.is_null());
1316 assert!(!is_xinclude_element(regular), "Regular elem not xinclude");
1317
1318 tree::free_doc(doc);
1319 }
1320 }
1321
1322 #[test]
1323 fn test_find_fallback_child() {
1332 unsafe {
1333 let (doc, root) = create_doc_with_xinclude_ns();
1334 let include = create_include_child(root, None, None);
1335 assert!(!include.is_null());
1336 let fallback = create_fallback_child(include);
1337 assert!(!fallback.is_null());
1338
1339 let found = find_fallback_child(include);
1340 assert!(!found.is_null(), "Should find fallback child");
1341
1342 let no_fallback = find_fallback_child(root);
1343 assert!(no_fallback.is_null(), "Root should not have fallback");
1344
1345 tree::free_doc(doc);
1346 }
1347 }
1348
1349 #[test]
1350 fn test_xml_str_equal() {
1359 unsafe {
1360 assert!(xml_str_equal(
1361 c"hello".as_ptr() as *const xmlChar,
1362 c"hello".as_ptr() as *const xmlChar,
1363 ));
1364 assert!(!xml_str_equal(
1365 c"hello".as_ptr() as *const xmlChar,
1366 c"world".as_ptr() as *const xmlChar,
1367 ));
1368 assert!(!xml_str_equal(
1369 ptr::null(),
1370 c"hello".as_ptr() as *const xmlChar
1371 ));
1372 assert!(!xml_str_equal(
1373 c"hello".as_ptr() as *const xmlChar,
1374 ptr::null()
1375 ));
1376 assert!(xml_str_equal(ptr::null(), ptr::null()));
1377 }
1378 }
1379
1380 #[test]
1381 fn test_xinclude_process_null_doc() {
1388 unsafe {
1389 assert_eq!(xinclude_process(ptr::null_mut()), XINCLUDE_FAILURE);
1390 }
1391 }
1392
1393 #[test]
1394 fn test_xinclude_process_no_includes() {
1402 unsafe {
1403 let doc = create_simple_doc();
1404 assert_eq!(xinclude_process(doc), 0);
1405 tree::free_doc(doc);
1406 }
1407 }
1408
1409 #[test]
1410 fn test_xinclude_process_with_includes() {
1419 unsafe {
1420 let (doc, root) = create_doc_with_xinclude_ns();
1422 create_include_child(root, Some(b"nonexistent.xml"), None);
1423 let result = xinclude_process(doc);
1424 assert!(result >= 0, "Should handle missing files: {}", result);
1425 tree::free_doc(doc);
1426 }
1427 }
1428
1429 #[test]
1430 fn test_xinclude_fallback_content() {
1439 unsafe {
1440 let (doc, root) = create_doc_with_xinclude_ns();
1441 let include = create_include_child(root, Some(b"nonexistent.xml"), None);
1442 let fb = create_fallback_child(include);
1443 let fb_child = tree::new_child(
1445 fb,
1446 ptr::null_mut(),
1447 c"fallback-elem".as_ptr() as *const xmlChar,
1448 );
1449 assert!(!fb_child.is_null());
1450
1451 let before = count_elements(doc, c"fallback-elem".as_ptr() as *const xmlChar);
1452 assert!(before > 0, "Should have fallback-elem before processing");
1453
1454 let result = xinclude_process(doc);
1455 assert!(result >= 0, "Should handle fallback: {}", result);
1456 tree::free_doc(doc);
1457 }
1458 }
1459
1460 #[test]
1461 fn test_xinclude_circular_reference_detection() {
1470 unsafe {
1471 let (doc, root) = create_doc_with_xinclude_ns();
1472 create_include_child(root, Some(b"self-ref.xml"), None);
1473 let result = xinclude_process(doc);
1474 assert!(result >= 0, "Circular ref should not crash: {}", result);
1475 tree::free_doc(doc);
1476 }
1477 }
1478
1479 #[test]
1480 fn test_xinclude_parse_attribute_detection() {
1489 unsafe {
1490 let (doc, root) = create_doc_with_xinclude_ns();
1491 create_include_child(root, Some(b"test.xml"), Some(b"xml"));
1492 create_include_child(root, Some(b"test.txt"), Some(b"text"));
1493 create_include_child(root, Some(b"default.xml"), None);
1494
1495 let mut count = 0;
1497 let mut child = (*root).children;
1498 while !child.is_null() {
1499 if is_xinclude_element(child) {
1500 count += 1;
1501 }
1502 child = (*child).next;
1503 }
1504 assert_eq!(count, 3, "Should have 3 include elements");
1505 tree::free_doc(doc);
1506 }
1507 }
1508
1509 #[test]
1510 fn test_xinclude_process_functions() {
1518 unsafe {
1519 let doc = create_simple_doc();
1520 let r1 = xinclude_process(doc);
1521 assert!(r1 >= 0);
1522 let r2 = xinclude_process_flags(doc, 0);
1523 assert!(r2 >= 0);
1524 tree::free_doc(doc);
1525 }
1526 }
1527
1528 #[test]
1529 fn test_xinclude_process_with_empty_href() {
1537 unsafe {
1538 let (doc, root) = create_doc_with_xinclude_ns();
1539 let include = create_include_child(root, None, None);
1540 create_fallback_child(include);
1541 let result = xinclude_process(doc);
1542 assert!(result >= 0, "Empty href with fallback: {}", result);
1543 tree::free_doc(doc);
1544 }
1545 }
1546
1547 #[test]
1548 fn test_set_doc_recursive() {
1558 unsafe {
1559 let doc = tree::new_doc(ptr::null());
1560 assert!(!doc.is_null());
1561 let parent = tree::new_child(
1562 doc as *mut _xmlNode,
1563 ptr::null_mut(),
1564 c"parent".as_ptr() as *const xmlChar,
1565 );
1566 assert!(!parent.is_null());
1567 let detached = tree::new_node(ptr::null_mut(), c"detached".as_ptr() as *const xmlChar);
1568 assert!(!detached.is_null());
1569 assert!((*detached).doc.is_null());
1570 set_doc_recursive(detached, doc);
1571 assert_eq!((*detached).doc, doc);
1572 tree::free_node(detached);
1573 tree::free_doc(doc);
1574 }
1575 }
1576
1577 #[test]
1578 fn test_find_root_element() {
1586 unsafe {
1587 let doc = create_simple_doc();
1588 let root = find_root_element(doc);
1589 assert!(!root.is_null());
1590 assert_eq!((*root).type_, XML_ELEMENT_NODE as c_int);
1591 tree::free_doc(doc);
1592 }
1593 }
1594
1595 #[test]
1596 fn test_xinclude_xpointer_attribute() {
1606 unsafe {
1607 let (doc, root) = create_doc_with_xinclude_ns();
1608 let include = create_include_child(root, Some(b"test.xml"), None);
1609 let xptr_val = crate::xml::string::bytes_to_xmlstr(b"xpointer(//target)");
1610 tree::set_prop(include, ATTR_XPOINTER.as_ptr() as *const xmlChar, xptr_val);
1611 allocator::xmlFreeImpl(xptr_val as *mut c_void);
1612
1613 let xptr = tree::get_prop(include, ATTR_XPOINTER.as_ptr() as *const xmlChar);
1614 assert!(!xptr.is_null(), "Should have xpointer attribute");
1615 assert_eq!(xmlstr_to_bytes(xptr), b"xpointer(//target)");
1616 allocator::xmlFreeImpl(xptr as *mut c_void);
1617
1618 tree::free_doc(doc);
1619 }
1620 }
1621
1622 #[test]
1623 fn test_xinclude_accept_attributes() {
1632 unsafe {
1633 let (doc, root) = create_doc_with_xinclude_ns();
1634 let include = create_include_child(root, Some(b"data.xml"), None);
1635
1636 let accept_val = crate::xml::string::bytes_to_xmlstr(b"application/xml");
1637 tree::set_prop(include, ATTR_ACCEPT.as_ptr() as *const xmlChar, accept_val);
1638 allocator::xmlFreeImpl(accept_val as *mut c_void);
1639
1640 let lang_val = crate::xml::string::bytes_to_xmlstr(b"en");
1641 tree::set_prop(
1642 include,
1643 ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar,
1644 lang_val,
1645 );
1646 allocator::xmlFreeImpl(lang_val as *mut c_void);
1647
1648 let accept = tree::get_prop(include, ATTR_ACCEPT.as_ptr() as *const xmlChar);
1649 assert!(!accept.is_null());
1650 assert_eq!(xmlstr_to_bytes(accept), b"application/xml");
1651 allocator::xmlFreeImpl(accept as *mut c_void);
1652
1653 let lang = tree::get_prop(include, ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar);
1654 assert!(!lang.is_null());
1655 assert_eq!(xmlstr_to_bytes(lang), b"en");
1656 allocator::xmlFreeImpl(lang as *mut c_void);
1657
1658 tree::free_doc(doc);
1659 }
1660 }
1661
1662 #[test]
1663 fn test_xinclude_encoding_attribute() {
1671 unsafe {
1672 let (doc, root) = create_doc_with_xinclude_ns();
1673 let include = create_include_child(root, Some(b"data.txt"), Some(b"text"));
1674
1675 let enc_val = crate::xml::string::bytes_to_xmlstr(b"UTF-8");
1676 tree::set_prop(include, ATTR_ENCODING.as_ptr() as *const xmlChar, enc_val);
1677 allocator::xmlFreeImpl(enc_val as *mut c_void);
1678
1679 let encoding = tree::get_prop(include, ATTR_ENCODING.as_ptr() as *const xmlChar);
1680 assert!(!encoding.is_null());
1681 assert_eq!(xmlstr_to_bytes(encoding), b"UTF-8");
1682 allocator::xmlFreeImpl(encoding as *mut c_void);
1683
1684 tree::free_doc(doc);
1685 }
1686 }
1687
1688 #[test]
1689 fn test_xinclude_process_flags_equivalence() {
1697 unsafe {
1698 let doc = create_simple_doc();
1699 let r1 = xinclude_process(doc);
1700 let r2 = xinclude_process_flags(doc, 0);
1701 assert_eq!(r1, r2);
1702 tree::free_doc(doc);
1703 }
1704 }
1705
1706 #[test]
1707 fn test_xinclude_process_flags_noxincnode() {
1714 unsafe {
1715 let doc = create_simple_doc();
1716 let result = xinclude_process_flags(doc, XML_PARSE_NOXINCNODE);
1717 assert_eq!(result, 0);
1718 tree::free_doc(doc);
1719 }
1720 }
1721
1722 #[test]
1723 #[ignore = "pre-existing tree module cleanup bug with modified trees"]
1724 fn test_complex_nested_includes_structure() {
1735 unsafe {
1736 let doc = tree::new_doc(ptr::null());
1738 assert!(!doc.is_null());
1739 let root = tree::new_child(
1740 doc as *mut _xmlNode,
1741 ptr::null_mut(),
1742 c"root".as_ptr() as *const xmlChar,
1743 );
1744 assert!(!root.is_null());
1745 create_ns(
1746 root,
1747 c"xi".as_ptr() as *const xmlChar,
1748 XINCLUDE_NS.as_ptr() as *const xmlChar,
1749 );
1750
1751 create_include_child(root, Some(b"nonexistent1.xml"), None);
1752
1753 let inc2 = create_include_child(root, Some(b"nonexistent2.xml"), None);
1754 let fb2 = create_fallback_child(inc2);
1755 tree::new_child(
1756 fb2,
1757 ptr::null_mut(),
1758 c"fallback-content".as_ptr() as *const xmlChar,
1759 );
1760
1761 create_include_child(root, Some(b"nonexistent3.txt"), Some(b"text"));
1762
1763 let result = xinclude_process(doc);
1764 assert!(result >= 0, "Complex structure: {}", result);
1765 }
1766 }
1767
1768 #[test]
1769 fn test_xinclude_process_xml_memory_cleanup() {
1777 unsafe {
1778 let doc = create_simple_doc();
1779 assert!(xinclude_process(doc) >= 0);
1780 tree::free_doc(doc);
1781 }
1782 }
1783
1784 #[test]
1785 fn test_mark_doc_xinclude_processed() {
1793 unsafe {
1794 let doc = create_simple_doc();
1795 assert_eq!((*doc).properties & XML_DOC_XINCLUDE as c_int, 0);
1796 mark_doc_xinclude_processed(doc);
1797 assert_ne!((*doc).properties & XML_DOC_XINCLUDE as c_int, 0);
1798 tree::free_doc(doc);
1799 }
1800 }
1801
1802 #[test]
1803 fn test_xinclude_xinclude_start_end_nodes() {
1814 unsafe {
1815 let doc = create_simple_doc();
1816 let root = find_root_element(doc);
1817 assert!(!root.is_null());
1818
1819 let sentinel =
1821 tree::new_node(ptr::null_mut(), c"XIncludeStart".as_ptr() as *const xmlChar);
1822 assert!(!sentinel.is_null());
1823 (*sentinel).type_ = XML_XINCLUDE_START as c_int;
1824 (*sentinel).doc = doc;
1825 let first_child = (*root).children;
1827 if !first_child.is_null() {
1828 (*sentinel).parent = root;
1830 (*sentinel).prev = first_child;
1831 (*sentinel).next = (*first_child).next;
1832 if !(*first_child).next.is_null() {
1833 (*(*first_child).next).prev = sentinel;
1834 }
1835 (*first_child).next = sentinel;
1836 if (*root).last == first_child {
1837 (*root).last = sentinel;
1838 }
1839 }
1840
1841 let mut visited = Vec::new();
1842 let count = { process_node_tree(root, doc, &mut visited) };
1843 assert_eq!(count, 0, "Should not process sentinel nodes");
1844
1845 if !(*sentinel).prev.is_null() {
1847 (*(*sentinel).prev).next = (*sentinel).next;
1848 }
1849 if !(*sentinel).next.is_null() {
1850 (*(*sentinel).next).prev = (*sentinel).prev;
1851 }
1852 (*sentinel).prev = ptr::null_mut();
1853 (*sentinel).next = ptr::null_mut();
1854 (*sentinel).parent = ptr::null_mut();
1855
1856 tree::free_node(sentinel);
1857 tree::free_doc(doc);
1858 }
1859 }
1860}