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)]
1025 unsafe fn create_doc_from_xml(xml: &[u8]) -> *mut _xmlDoc {
1035 let doc = unsafe {
1036 crate::abi::exports_xml2::xmlReadMemory(
1037 xml.as_ptr() as *const c_char,
1038 xml.len() as c_int,
1039 ptr::null(),
1040 ptr::null(),
1041 0,
1042 )
1043 };
1044 if doc.is_null() {
1045 return ptr::null_mut();
1046 }
1047 doc
1048 }
1049
1050 unsafe fn create_simple_doc() -> *mut _xmlDoc {
1052 let doc = tree::new_doc(ptr::null());
1053 assert!(!doc.is_null(), "Failed to create doc");
1054
1055 let root = tree::new_child(
1056 doc as *mut _xmlNode,
1057 ptr::null_mut(),
1058 c"root".as_ptr() as *const xmlChar,
1059 );
1060 assert!(!root.is_null(), "Failed to create root");
1061
1062 doc
1063 }
1064
1065 unsafe fn create_ns(
1067 node: *mut _xmlNode,
1068 prefix: *const xmlChar,
1069 href: *const xmlChar,
1070 ) -> *mut _xmlNs {
1071 tree::new_ns(node, href, prefix)
1072 }
1073
1074 unsafe fn create_doc_with_xinclude_ns() -> (*mut _xmlDoc, *mut _xmlNode) {
1076 let doc = tree::new_doc(ptr::null());
1077 assert!(!doc.is_null());
1078 let root = tree::new_child(
1079 doc as *mut _xmlNode,
1080 ptr::null_mut(),
1081 c"root".as_ptr() as *const xmlChar,
1082 );
1083 assert!(!root.is_null());
1084 create_ns(
1085 root,
1086 c"xi".as_ptr() as *const xmlChar,
1087 XINCLUDE_NS.as_ptr() as *const xmlChar,
1088 );
1089 (doc, root)
1090 }
1091
1092 unsafe fn create_include_child(
1094 parent: *mut _xmlNode,
1095 href: Option<&[u8]>,
1096 parse: Option<&[u8]>,
1097 ) -> *mut _xmlNode {
1098 let ns = create_ns(
1099 parent,
1100 c"xi".as_ptr() as *const xmlChar,
1101 XINCLUDE_NS.as_ptr() as *const xmlChar,
1102 );
1103 let elem = tree::new_child(parent, ns, c"include".as_ptr() as *const xmlChar);
1104 if let Some(h) = href {
1105 let h_str = crate::xml::string::bytes_to_xmlstr(h);
1106 tree::set_prop(elem, ATTR_HREF.as_ptr() as *const xmlChar, h_str);
1107 allocator::xmlFreeImpl(h_str as *mut c_void);
1108 }
1109 if let Some(p) = parse {
1110 let p_str = crate::xml::string::bytes_to_xmlstr(p);
1111 tree::set_prop(elem, ATTR_PARSE.as_ptr() as *const xmlChar, p_str);
1112 allocator::xmlFreeImpl(p_str as *mut c_void);
1113 }
1114 elem
1115 }
1116
1117 unsafe fn create_fallback_child(parent: *mut _xmlNode) -> *mut _xmlNode {
1119 let ns = create_ns(
1120 parent,
1121 c"xi".as_ptr() as *const xmlChar,
1122 XINCLUDE_NS.as_ptr() as *const xmlChar,
1123 );
1124 tree::new_child(parent, ns, c"fallback".as_ptr() as *const xmlChar)
1125 }
1126 #[allow(dead_code)]
1127 unsafe fn find_element(doc: *mut _xmlDoc, name: *const xmlChar) -> *mut _xmlNode {
1136 if doc.is_null() {
1137 return ptr::null_mut();
1138 }
1139 let mut child = unsafe { (*doc).children };
1140 while !child.is_null() {
1141 let result = unsafe { find_element_recursive(child, name) };
1142 if !result.is_null() {
1143 return result;
1144 }
1145 child = unsafe { (*child).next };
1146 }
1147 ptr::null_mut()
1148 }
1149
1150 #[allow(dead_code)]
1151 unsafe fn find_element_recursive(node: *mut _xmlNode, name: *const xmlChar) -> *mut _xmlNode {
1160 if node.is_null() {
1161 return ptr::null_mut();
1162 }
1163 let n = unsafe { &*node };
1164 if n.type_ == XML_ELEMENT_NODE as c_int
1165 && !n.name.is_null()
1166 && unsafe { xml_str_equal(n.name, name) }
1167 {
1168 return node;
1169 }
1170 let mut child = n.children;
1171 while !child.is_null() {
1172 let result = unsafe { find_element_recursive(child, name) };
1173 if !result.is_null() {
1174 return result;
1175 }
1176 child = unsafe { (*child).next };
1177 }
1178 ptr::null_mut()
1179 }
1180
1181 unsafe fn count_elements(doc: *mut _xmlDoc, name: *const xmlChar) -> c_int {
1190 if doc.is_null() {
1191 return 0;
1192 }
1193 let mut count: c_int = 0;
1194 let mut child = unsafe { (*doc).children };
1195 while !child.is_null() {
1196 count += unsafe { count_elements_recursive(child, name) };
1197 child = unsafe { (*child).next };
1198 }
1199 count
1200 }
1201
1202 unsafe fn count_elements_recursive(node: *mut _xmlNode, name: *const xmlChar) -> c_int {
1211 if node.is_null() {
1212 return 0;
1213 }
1214 let mut count: c_int = 0;
1215 let n = unsafe { &*node };
1216 if n.type_ == XML_ELEMENT_NODE as c_int
1217 && !n.name.is_null()
1218 && unsafe { xml_str_equal(n.name, name) }
1219 {
1220 count += 1;
1221 }
1222 let mut child = n.children;
1223 while !child.is_null() {
1224 count += unsafe { count_elements_recursive(child, name) };
1225 child = unsafe { (*child).next };
1226 }
1227 count
1228 }
1229
1230 #[test]
1235 fn test_is_xinclude_element() {
1244 unsafe {
1245 let doc = create_simple_doc();
1246 assert!(!doc.is_null());
1247 let root = (*doc).children;
1248 assert!(!root.is_null());
1249 assert!(!is_xinclude_element(root));
1250 assert!(!is_xinclude_element(ptr::null_mut()));
1251 tree::free_doc(doc);
1252 }
1253 }
1254
1255 #[test]
1256 fn test_xinclude_namespace_detection() {
1265 unsafe {
1266 let (doc, root) = create_doc_with_xinclude_ns();
1267 let include = create_include_child(root, Some(b"test.xml"), None);
1268 assert!(!include.is_null());
1269 assert!(is_xinclude_element(include), "Should detect xi:include");
1270
1271 let regular =
1273 tree::new_child(root, ptr::null_mut(), c"regular".as_ptr() as *const xmlChar);
1274 assert!(!regular.is_null());
1275 assert!(!is_xinclude_element(regular), "Regular elem not xinclude");
1276
1277 tree::free_doc(doc);
1278 }
1279 }
1280
1281 #[test]
1282 fn test_find_fallback_child() {
1291 unsafe {
1292 let (doc, root) = create_doc_with_xinclude_ns();
1293 let include = create_include_child(root, None, None);
1294 assert!(!include.is_null());
1295 let fallback = create_fallback_child(include);
1296 assert!(!fallback.is_null());
1297
1298 let found = find_fallback_child(include);
1299 assert!(!found.is_null(), "Should find fallback child");
1300
1301 let no_fallback = find_fallback_child(root);
1302 assert!(no_fallback.is_null(), "Root should not have fallback");
1303
1304 tree::free_doc(doc);
1305 }
1306 }
1307
1308 #[test]
1309 fn test_xml_str_equal() {
1318 unsafe {
1319 assert!(xml_str_equal(
1320 c"hello".as_ptr() as *const xmlChar,
1321 c"hello".as_ptr() as *const xmlChar,
1322 ));
1323 assert!(!xml_str_equal(
1324 c"hello".as_ptr() as *const xmlChar,
1325 c"world".as_ptr() as *const xmlChar,
1326 ));
1327 assert!(!xml_str_equal(
1328 ptr::null(),
1329 c"hello".as_ptr() as *const xmlChar
1330 ));
1331 assert!(!xml_str_equal(
1332 c"hello".as_ptr() as *const xmlChar,
1333 ptr::null()
1334 ));
1335 assert!(xml_str_equal(ptr::null(), ptr::null()));
1336 }
1337 }
1338
1339 #[test]
1340 fn test_xinclude_process_null_doc() {
1347 unsafe {
1348 assert_eq!(xinclude_process(ptr::null_mut()), XINCLUDE_FAILURE);
1349 }
1350 }
1351
1352 #[test]
1353 fn test_xinclude_process_no_includes() {
1361 unsafe {
1362 let doc = create_simple_doc();
1363 assert_eq!(xinclude_process(doc), 0);
1364 tree::free_doc(doc);
1365 }
1366 }
1367
1368 #[test]
1369 fn test_xinclude_process_with_includes() {
1378 unsafe {
1379 let (doc, root) = create_doc_with_xinclude_ns();
1381 create_include_child(root, Some(b"nonexistent.xml"), None);
1382 let result = xinclude_process(doc);
1383 assert!(result >= 0, "Should handle missing files: {}", result);
1384 tree::free_doc(doc);
1385 }
1386 }
1387
1388 #[test]
1389 fn test_xinclude_fallback_content() {
1398 unsafe {
1399 let (doc, root) = create_doc_with_xinclude_ns();
1400 let include = create_include_child(root, Some(b"nonexistent.xml"), None);
1401 let fb = create_fallback_child(include);
1402 let fb_child = tree::new_child(
1404 fb,
1405 ptr::null_mut(),
1406 c"fallback-elem".as_ptr() as *const xmlChar,
1407 );
1408 assert!(!fb_child.is_null());
1409
1410 let before = count_elements(doc, c"fallback-elem".as_ptr() as *const xmlChar);
1411 assert!(before > 0, "Should have fallback-elem before processing");
1412
1413 let result = xinclude_process(doc);
1414 assert!(result >= 0, "Should handle fallback: {}", result);
1415 tree::free_doc(doc);
1416 }
1417 }
1418
1419 #[test]
1420 fn test_xinclude_circular_reference_detection() {
1429 unsafe {
1430 let (doc, root) = create_doc_with_xinclude_ns();
1431 create_include_child(root, Some(b"self-ref.xml"), None);
1432 let result = xinclude_process(doc);
1433 assert!(result >= 0, "Circular ref should not crash: {}", result);
1434 tree::free_doc(doc);
1435 }
1436 }
1437
1438 #[test]
1439 fn test_xinclude_parse_attribute_detection() {
1448 unsafe {
1449 let (doc, root) = create_doc_with_xinclude_ns();
1450 create_include_child(root, Some(b"test.xml"), Some(b"xml"));
1451 create_include_child(root, Some(b"test.txt"), Some(b"text"));
1452 create_include_child(root, Some(b"default.xml"), None);
1453
1454 let mut count = 0;
1456 let mut child = (*root).children;
1457 while !child.is_null() {
1458 if is_xinclude_element(child) {
1459 count += 1;
1460 }
1461 child = (*child).next;
1462 }
1463 assert_eq!(count, 3, "Should have 3 include elements");
1464 tree::free_doc(doc);
1465 }
1466 }
1467
1468 #[test]
1469 fn test_xinclude_process_functions() {
1477 unsafe {
1478 let doc = create_simple_doc();
1479 let r1 = xinclude_process(doc);
1480 assert!(r1 >= 0);
1481 let r2 = xinclude_process_flags(doc, 0);
1482 assert!(r2 >= 0);
1483 tree::free_doc(doc);
1484 }
1485 }
1486
1487 #[test]
1488 fn test_xinclude_process_with_empty_href() {
1496 unsafe {
1497 let (doc, root) = create_doc_with_xinclude_ns();
1498 let include = create_include_child(root, None, None);
1499 create_fallback_child(include);
1500 let result = xinclude_process(doc);
1501 assert!(result >= 0, "Empty href with fallback: {}", result);
1502 tree::free_doc(doc);
1503 }
1504 }
1505
1506 #[test]
1507 fn test_set_doc_recursive() {
1517 unsafe {
1518 let doc = tree::new_doc(ptr::null());
1519 assert!(!doc.is_null());
1520 let parent = tree::new_child(
1521 doc as *mut _xmlNode,
1522 ptr::null_mut(),
1523 c"parent".as_ptr() as *const xmlChar,
1524 );
1525 assert!(!parent.is_null());
1526 let detached = tree::new_node(ptr::null_mut(), c"detached".as_ptr() as *const xmlChar);
1527 assert!(!detached.is_null());
1528 assert!((*detached).doc.is_null());
1529 set_doc_recursive(detached, doc);
1530 assert_eq!((*detached).doc, doc);
1531 tree::free_node(detached);
1532 tree::free_doc(doc);
1533 }
1534 }
1535
1536 #[test]
1537 fn test_find_root_element() {
1545 unsafe {
1546 let doc = create_simple_doc();
1547 let root = find_root_element(doc);
1548 assert!(!root.is_null());
1549 assert_eq!((*root).type_, XML_ELEMENT_NODE as c_int);
1550 tree::free_doc(doc);
1551 }
1552 }
1553
1554 #[test]
1555 fn test_xinclude_xpointer_attribute() {
1565 unsafe {
1566 let (doc, root) = create_doc_with_xinclude_ns();
1567 let include = create_include_child(root, Some(b"test.xml"), None);
1568 let xptr_val = crate::xml::string::bytes_to_xmlstr(b"xpointer(//target)");
1569 tree::set_prop(include, ATTR_XPOINTER.as_ptr() as *const xmlChar, xptr_val);
1570 allocator::xmlFreeImpl(xptr_val as *mut c_void);
1571
1572 let xptr = tree::get_prop(include, ATTR_XPOINTER.as_ptr() as *const xmlChar);
1573 assert!(!xptr.is_null(), "Should have xpointer attribute");
1574 assert_eq!(xmlstr_to_bytes(xptr), b"xpointer(//target)");
1575 allocator::xmlFreeImpl(xptr as *mut c_void);
1576
1577 tree::free_doc(doc);
1578 }
1579 }
1580
1581 #[test]
1582 fn test_xinclude_accept_attributes() {
1591 unsafe {
1592 let (doc, root) = create_doc_with_xinclude_ns();
1593 let include = create_include_child(root, Some(b"data.xml"), None);
1594
1595 let accept_val = crate::xml::string::bytes_to_xmlstr(b"application/xml");
1596 tree::set_prop(include, ATTR_ACCEPT.as_ptr() as *const xmlChar, accept_val);
1597 allocator::xmlFreeImpl(accept_val as *mut c_void);
1598
1599 let lang_val = crate::xml::string::bytes_to_xmlstr(b"en");
1600 tree::set_prop(
1601 include,
1602 ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar,
1603 lang_val,
1604 );
1605 allocator::xmlFreeImpl(lang_val as *mut c_void);
1606
1607 let accept = tree::get_prop(include, ATTR_ACCEPT.as_ptr() as *const xmlChar);
1608 assert!(!accept.is_null());
1609 assert_eq!(xmlstr_to_bytes(accept), b"application/xml");
1610 allocator::xmlFreeImpl(accept as *mut c_void);
1611
1612 let lang = tree::get_prop(include, ATTR_ACCEPT_LANGUAGE.as_ptr() as *const xmlChar);
1613 assert!(!lang.is_null());
1614 assert_eq!(xmlstr_to_bytes(lang), b"en");
1615 allocator::xmlFreeImpl(lang as *mut c_void);
1616
1617 tree::free_doc(doc);
1618 }
1619 }
1620
1621 #[test]
1622 fn test_xinclude_encoding_attribute() {
1630 unsafe {
1631 let (doc, root) = create_doc_with_xinclude_ns();
1632 let include = create_include_child(root, Some(b"data.txt"), Some(b"text"));
1633
1634 let enc_val = crate::xml::string::bytes_to_xmlstr(b"UTF-8");
1635 tree::set_prop(include, ATTR_ENCODING.as_ptr() as *const xmlChar, enc_val);
1636 allocator::xmlFreeImpl(enc_val as *mut c_void);
1637
1638 let encoding = tree::get_prop(include, ATTR_ENCODING.as_ptr() as *const xmlChar);
1639 assert!(!encoding.is_null());
1640 assert_eq!(xmlstr_to_bytes(encoding), b"UTF-8");
1641 allocator::xmlFreeImpl(encoding as *mut c_void);
1642
1643 tree::free_doc(doc);
1644 }
1645 }
1646
1647 #[test]
1648 fn test_xinclude_process_flags_equivalence() {
1656 unsafe {
1657 let doc = create_simple_doc();
1658 let r1 = xinclude_process(doc);
1659 let r2 = xinclude_process_flags(doc, 0);
1660 assert_eq!(r1, r2);
1661 tree::free_doc(doc);
1662 }
1663 }
1664
1665 #[test]
1666 fn test_xinclude_process_flags_noxincnode() {
1673 unsafe {
1674 let doc = create_simple_doc();
1675 let result = xinclude_process_flags(doc, XML_PARSE_NOXINCNODE);
1676 assert_eq!(result, 0);
1677 tree::free_doc(doc);
1678 }
1679 }
1680
1681 #[test]
1682 #[ignore = "pre-existing tree module cleanup bug with modified trees"]
1683 fn test_complex_nested_includes_structure() {
1694 unsafe {
1695 let doc = tree::new_doc(ptr::null());
1697 assert!(!doc.is_null());
1698 let root = tree::new_child(
1699 doc as *mut _xmlNode,
1700 ptr::null_mut(),
1701 c"root".as_ptr() as *const xmlChar,
1702 );
1703 assert!(!root.is_null());
1704 create_ns(
1705 root,
1706 c"xi".as_ptr() as *const xmlChar,
1707 XINCLUDE_NS.as_ptr() as *const xmlChar,
1708 );
1709
1710 create_include_child(root, Some(b"nonexistent1.xml"), None);
1711
1712 let inc2 = create_include_child(root, Some(b"nonexistent2.xml"), None);
1713 let fb2 = create_fallback_child(inc2);
1714 tree::new_child(
1715 fb2,
1716 ptr::null_mut(),
1717 c"fallback-content".as_ptr() as *const xmlChar,
1718 );
1719
1720 create_include_child(root, Some(b"nonexistent3.txt"), Some(b"text"));
1721
1722 let result = xinclude_process(doc);
1723 assert!(result >= 0, "Complex structure: {}", result);
1724 }
1725 }
1726
1727 #[test]
1728 fn test_xinclude_process_xml_memory_cleanup() {
1736 unsafe {
1737 let doc = create_simple_doc();
1738 assert!(xinclude_process(doc) >= 0);
1739 tree::free_doc(doc);
1740 }
1741 }
1742
1743 #[test]
1744 fn test_mark_doc_xinclude_processed() {
1752 unsafe {
1753 let doc = create_simple_doc();
1754 assert_eq!((*doc).properties & XML_DOC_XINCLUDE as c_int, 0);
1755 mark_doc_xinclude_processed(doc);
1756 assert_ne!((*doc).properties & XML_DOC_XINCLUDE as c_int, 0);
1757 tree::free_doc(doc);
1758 }
1759 }
1760
1761 #[test]
1762 fn test_xinclude_xinclude_start_end_nodes() {
1773 unsafe {
1774 let doc = create_simple_doc();
1775 let root = find_root_element(doc);
1776 assert!(!root.is_null());
1777
1778 let sentinel =
1780 tree::new_node(ptr::null_mut(), c"XIncludeStart".as_ptr() as *const xmlChar);
1781 assert!(!sentinel.is_null());
1782 (*sentinel).type_ = XML_XINCLUDE_START as c_int;
1783 (*sentinel).doc = doc;
1784 let first_child = (*root).children;
1786 if !first_child.is_null() {
1787 (*sentinel).parent = root;
1789 (*sentinel).prev = first_child;
1790 (*sentinel).next = (*first_child).next;
1791 if !(*first_child).next.is_null() {
1792 (*(*first_child).next).prev = sentinel;
1793 }
1794 (*first_child).next = sentinel;
1795 if (*root).last == first_child {
1796 (*root).last = sentinel;
1797 }
1798 }
1799
1800 let mut visited = Vec::new();
1801 let count = { process_node_tree(root, doc, &mut visited) };
1802 assert_eq!(count, 0, "Should not process sentinel nodes");
1803
1804 if !(*sentinel).prev.is_null() {
1806 (*(*sentinel).prev).next = (*sentinel).next;
1807 }
1808 if !(*sentinel).next.is_null() {
1809 (*(*sentinel).next).prev = (*sentinel).prev;
1810 }
1811 (*sentinel).prev = ptr::null_mut();
1812 (*sentinel).next = ptr::null_mut();
1813 (*sentinel).parent = ptr::null_mut();
1814
1815 tree::free_node(sentinel);
1816 tree::free_doc(doc);
1817 }
1818 }
1819}