1use crate::abi::structs::{_xmlAttr, _xmlDoc, _xmlNode};
69use crate::abi::types::xmlAttributeType::XML_ATTRIBUTE_ID;
70use crate::abi::types::xmlElementType::{XML_ELEMENT_NODE, XML_TEXT_NODE};
71use crate::xml::xpath::context::XPathContext;
72use crate::xml::xpath::types::NodeSet;
73use std::ffi::CStr;
74
75#[cfg(test)]
76use std::ffi::CString;
77use std::os::raw::c_char;
78use std::ptr;
79
80pub unsafe fn xptr_eval(expr: &str, doc: *mut _xmlDoc) -> Option<*mut _xmlNode> {
102 if doc.is_null() {
103 return None;
104 }
105
106 let expr = expr.trim();
107
108 if expr.is_empty() {
109 return None;
110 }
111
112 if let Some(result) = try_eval_scheme(expr, doc) {
114 return result;
115 }
116
117 shorthand_lookup(expr, doc)
119}
120
121pub unsafe fn xptr_eval_with_context(
130 expr: &str,
131 doc: *mut _xmlDoc,
132 _context: Option<&mut XPathContext>,
133) -> Option<*mut _xmlNode> {
134 xptr_eval(expr, doc)
135}
136
137pub unsafe extern "C" fn xmlXPtrEval(expr: *const c_char, doc: *mut _xmlDoc) -> *mut _xmlNode {
153 if expr.is_null() || doc.is_null() {
154 return ptr::null_mut();
155 }
156
157 let expr_str = match unsafe { CStr::from_ptr(expr) }.to_str() {
158 Ok(s) => s,
159 Err(_) => return ptr::null_mut(),
160 };
161
162 match unsafe { xptr_eval(expr_str, doc) } {
163 Some(node) => node,
164 None => ptr::null_mut(),
165 }
166}
167
168#[no_mangle]
177pub unsafe extern "C" fn xmlXPtrEvalNodeSet(
178 expr: *const c_char,
179 doc: *mut _xmlDoc,
180) -> *mut crate::abi::structs::_xmlNodeSet {
181 if expr.is_null() || doc.is_null() {
182 return ptr::null_mut();
183 }
184
185 let expr_str = match unsafe { CStr::from_ptr(expr) }.to_str() {
186 Ok(s) => s,
187 Err(_) => return ptr::null_mut(),
188 };
189
190 let node = unsafe { xptr_eval(expr_str, doc) };
191
192 let mut ns = NodeSet::new();
193 if let Some(n) = node {
194 ns.push(n);
195 }
196
197 unsafe { ns.to_raw() }
198}
199
200unsafe fn try_eval_scheme(expr: &str, doc: *mut _xmlDoc) -> Option<Option<*mut _xmlNode>> {
208 let expr = expr.trim();
209
210 if let Some(inner) = strip_scheme(expr, "element") {
212 return Some(unsafe { eval_element_scheme(inner, doc) });
213 }
214
215 None
218}
219
220fn strip_scheme<'a>(expr: &'a str, scheme: &str) -> Option<&'a str> {
225 let expr = expr.trim();
226
227 let expected_prefix = format!("{}(", scheme);
228 if !expr.starts_with(&expected_prefix) {
229 return None;
230 }
231
232 let inner_start = expected_prefix.len();
233 if !expr.ends_with(')') {
234 return None;
235 }
236
237 let inner_end = expr.len() - 1;
238 if inner_end <= inner_start {
239 return Some("");
240 }
241
242 Some(&expr[inner_start..inner_end])
243}
244
245unsafe fn eval_element_scheme(inner: &str, doc: *mut _xmlDoc) -> Option<*mut _xmlNode> {
258 let inner = inner.trim();
259 if inner.is_empty() {
260 return None;
261 }
262
263 let parts: Vec<&str> = inner.split('/').collect();
265 if parts.is_empty() {
266 return None;
267 }
268
269 let id = parts[0].trim();
270 if id.is_empty() {
271 return None;
272 }
273
274 let base = unsafe { find_element_by_id(id, doc) }?;
276
277 if parts.len() == 1 {
279 return Some(base);
280 }
281
282 let mut current = base;
284 for &part in &parts[1..] {
285 let index_str = part.trim();
286 let index: usize = match index_str.parse() {
287 Ok(n) if n >= 1 => n,
288 _ => return None,
289 };
290
291 current = unsafe { nth_child_element(current, index) }?;
293 }
294
295 Some(current)
296}
297
298unsafe fn nth_child_element(node: *mut _xmlNode, n: usize) -> Option<*mut _xmlNode> {
302 if node.is_null() {
303 return None;
304 }
305
306 let mut count = 0usize;
307 let mut child = unsafe { (*node).children };
308
309 while !child.is_null() {
310 let ty = unsafe { (*child).type_ };
311 if ty == XML_ELEMENT_NODE as std::os::raw::c_int {
312 count += 1;
313 if count == n {
314 return Some(child);
315 }
316 }
317 child = unsafe { (*child).next };
318 }
319
320 None
321}
322
323unsafe fn shorthand_lookup(name: &str, doc: *mut _xmlDoc) -> Option<*mut _xmlNode> {
332 unsafe { find_element_by_id(name, doc) }
333}
334
335unsafe fn find_element_by_id(id: &str, doc: *mut _xmlDoc) -> Option<*mut _xmlNode> {
356 if doc.is_null() || id.is_empty() {
357 return None;
358 }
359
360 let root = unsafe { (*doc).children };
363 if root.is_null() {
364 return None;
365 }
366
367 unsafe { walk_for_id(root, id) }
368}
369
370unsafe fn walk_for_id(node: *mut _xmlNode, id: &str) -> Option<*mut _xmlNode> {
372 if node.is_null() {
373 return None;
374 }
375
376 let ty = unsafe { (*node).type_ };
378 if ty == XML_ELEMENT_NODE as std::os::raw::c_int && unsafe { element_has_id(node, id) } {
379 return Some(node);
380 }
381
382 let mut child = unsafe { (*node).children };
384 while !child.is_null() {
385 if let Some(found) = unsafe { walk_for_id(child, id) } {
386 return Some(found);
387 }
388 child = unsafe { (*child).next };
389 }
390
391 None
392}
393
394unsafe fn element_has_id(node: *mut _xmlNode, id: &str) -> bool {
400 if node.is_null() {
401 return false;
402 }
403
404 let mut prop = unsafe { (*node).properties };
405 while !prop.is_null() {
406 let attr = unsafe { &*prop };
407
408 if attr.atype == XML_ATTRIBUTE_ID as std::os::raw::c_int {
410 if let Some(val) = unsafe { get_attr_value(prop) } {
411 if val == id {
412 return true;
413 }
414 }
415 }
416
417 if !attr.name.is_null() {
419 let name_str = unsafe { c_xmlchar_to_str(attr.name) };
420 if name_str.as_deref() == Some("id") || name_str.as_deref() == Some("ID") {
421 if let Some(val) = unsafe { get_attr_value(prop) } {
422 if val == id {
423 return true;
424 }
425 }
426 }
427 }
428
429 prop = unsafe { (*prop).next };
430 }
431
432 false
433}
434
435unsafe fn get_attr_value(attr: *mut _xmlAttr) -> Option<String> {
437 if attr.is_null() {
438 return None;
439 }
440
441 let children = unsafe { (*attr).children };
442 if children.is_null() {
443 return None;
444 }
445
446 let text = unsafe { &*children };
447 if text.type_ == XML_TEXT_NODE as std::os::raw::c_int && !text.content.is_null() {
448 let val = unsafe { c_xmlchar_to_str(text.content) };
449 return val;
450 }
451
452 None
453}
454
455unsafe fn c_xmlchar_to_str(ptr: *const crate::abi::types::xmlChar) -> Option<String> {
459 if ptr.is_null() {
460 return None;
461 }
462
463 let c_str = unsafe { CStr::from_ptr(ptr as *const c_char) };
465 match c_str.to_str() {
466 Ok(s) => Some(s.to_string()),
467 Err(_) => None,
468 }
469}
470
471#[cfg(test)]
476mod tests {
477 use super::*;
478 use crate::abi::allocator::xmlMallocZero;
479 use crate::abi::types::xmlElementType::*;
480 use std::mem;
481 use std::os::raw::c_int;
482 use std::ptr;
483
484 unsafe fn create_simple_doc() -> *mut _xmlDoc {
488 let doc = xmlMallocZero(mem::size_of::<_xmlDoc>()) as *mut _xmlDoc;
489 assert!(!doc.is_null());
490
491 let root = xmlMallocZero(mem::size_of::<_xmlNode>()) as *mut _xmlNode;
492 assert!(!root.is_null());
493
494 unsafe {
495 (*doc).type_ = XML_DOCUMENT_NODE as c_int;
496 (*doc).doc = doc;
497 (*doc).children = root;
498
499 (*root).type_ = XML_ELEMENT_NODE as c_int;
500 (*root).name = string_to_xmlchar("root");
501 (*root).parent = doc as *mut _xmlNode;
502 (*root).doc = doc;
503 (*root).properties = ptr::null_mut();
504 }
505
506 let attr = unsafe { add_id_attr(root, "id", "main") };
508 unsafe {
509 (*root).properties = attr;
510 }
511
512 doc
513 }
514
515 unsafe fn create_complex_doc() -> *mut _xmlDoc {
526 let doc = xmlMallocZero(mem::size_of::<_xmlDoc>()) as *mut _xmlDoc;
527 assert!(!doc.is_null());
528
529 let root = xmlMallocZero(mem::size_of::<_xmlNode>()) as *mut _xmlNode;
531 assert!(!root.is_null());
532
533 unsafe {
534 (*doc).type_ = XML_DOCUMENT_NODE as c_int;
535 (*doc).doc = doc;
536 (*doc).children = root;
537
538 (*root).type_ = XML_ELEMENT_NODE as c_int;
539 (*root).name = string_to_xmlchar("root");
540 (*root).parent = doc as *mut _xmlNode;
541 (*root).doc = doc;
542 }
543
544 let attr_root = unsafe { add_id_attr(root, "id", "main") };
545 unsafe { (*root).properties = attr_root };
546
547 let child1 = unsafe { append_child_element(root, "child1") };
549 let attr_c1 = unsafe { add_id_attr(child1, "id", "a") };
550 unsafe { (*child1).properties = attr_c1 };
551
552 let child2 = unsafe { append_child_element(root, "child2") };
554 let attr_c2 = unsafe { add_id_attr(child2, "id", "b") };
555 unsafe { (*child2).properties = attr_c2 };
556
557 let grandchild = unsafe { append_child_element(child2, "grandchild") };
559 let attr_gc = unsafe { add_id_attr(grandchild, "id", "c") };
560 unsafe { (*grandchild).properties = attr_gc };
561
562 let _child3 = unsafe { append_child_element(root, "child3") };
564
565 doc
566 }
567
568 unsafe fn string_to_xmlchar(s: &str) -> *const crate::abi::types::xmlChar {
569 let c_str = CString::new(s).unwrap();
570 c_str.into_raw() as *const crate::abi::types::xmlChar
571 }
572
573 unsafe fn append_child_element(parent: *mut _xmlNode, name: &str) -> *mut _xmlNode {
574 let node = xmlMallocZero(mem::size_of::<_xmlNode>()) as *mut _xmlNode;
575 assert!(!node.is_null());
576
577 unsafe {
578 (*node).type_ = XML_ELEMENT_NODE as c_int;
579 (*node).name = string_to_xmlchar(name);
580 (*node).parent = parent;
581 (*node).doc = (*parent).doc;
582 (*node).next = ptr::null_mut();
583 (*node).prev = (*parent).last;
584 (*node).properties = ptr::null_mut();
585
586 if (*parent).children.is_null() {
588 (*parent).children = node;
589 (*parent).last = node;
590 } else {
591 let last = (*parent).last;
592 if !last.is_null() {
593 (*last).next = node;
594 }
595 (*parent).last = node;
596 }
597 }
598
599 node
600 }
601
602 unsafe fn add_id_attr(node: *mut _xmlNode, name: &str, value: &str) -> *mut _xmlAttr {
603 let attr = xmlMallocZero(mem::size_of::<_xmlAttr>()) as *mut _xmlAttr;
604 assert!(!attr.is_null());
605
606 let text = xmlMallocZero(mem::size_of::<_xmlNode>()) as *mut _xmlNode;
608 assert!(!text.is_null());
609
610 unsafe {
611 (*attr).type_ = 2; (*attr).name = string_to_xmlchar(name);
613 (*attr).parent = node;
614 (*attr).doc = (*node).doc;
615 (*attr).children = text;
616 (*attr).last = text;
617 (*attr).atype = crate::abi::types::xmlAttributeType::XML_ATTRIBUTE_CDATA as c_int;
618 (*attr).next = ptr::null_mut();
619 (*attr).prev = ptr::null_mut();
620
621 (*text).type_ = XML_TEXT_NODE as c_int;
622 (*text).name = string_to_xmlchar("text");
623 (*text).content = string_to_xmlchar(value) as *mut crate::abi::types::xmlChar;
624 (*text).parent = attr as *mut _xmlNode;
625 (*text).doc = (*node).doc;
626 (*text).next = ptr::null_mut();
627 (*text).prev = ptr::null_mut();
628 }
629
630 attr
631 }
632
633 macro_rules! c_name_eq {
636 ($node:expr, $expected:expr) => {
637 assert_eq!(
638 CStr::from_ptr((*$node).name as *const c_char)
639 .to_str()
640 .unwrap(),
641 $expected
642 );
643 };
644 }
645
646 #[test]
647 fn test_shorthand_pointer() {
648 unsafe {
649 let doc = create_simple_doc();
650 let result = xptr_eval("main", doc);
651 assert!(result.is_some());
652 c_name_eq!(result.unwrap(), "root");
653 }
654 }
655
656 #[test]
657 fn test_shorthand_pointer_not_found() {
658 unsafe {
659 let doc = create_simple_doc();
660 let result = xptr_eval("nonexistent", doc);
661 assert!(result.is_none());
662 }
663 }
664
665 #[test]
666 fn test_element_scheme_basic() {
667 unsafe {
668 let doc = create_complex_doc();
669
670 let result = xptr_eval("element(main)", doc);
671 assert!(result.is_some());
672 c_name_eq!(result.unwrap(), "root");
673
674 let result = xptr_eval("element(a)", doc);
675 assert!(result.is_some());
676 c_name_eq!(result.unwrap(), "child1");
677
678 let result = xptr_eval("element(c)", doc);
679 assert!(result.is_some());
680 c_name_eq!(result.unwrap(), "grandchild");
681 }
682 }
683
684 #[test]
685 fn test_element_scheme_with_child_sequence() {
686 unsafe {
687 let doc = create_complex_doc();
688
689 let result = xptr_eval("element(main/1)", doc);
690 assert!(result.is_some());
691 c_name_eq!(result.unwrap(), "child1");
692
693 let result = xptr_eval("element(main/2)", doc);
694 assert!(result.is_some());
695 c_name_eq!(result.unwrap(), "child2");
696
697 let result = xptr_eval("element(main/2/1)", doc);
698 assert!(result.is_some());
699 c_name_eq!(result.unwrap(), "grandchild");
700 }
701 }
702
703 #[test]
704 fn test_element_scheme_child_out_of_range() {
705 unsafe {
706 let doc = create_complex_doc();
707 let result = xptr_eval("element(main/99)", doc);
708 assert!(result.is_none());
709 }
710 }
711
712 #[test]
713 fn test_element_scheme_zero_index() {
714 unsafe {
715 let doc = create_complex_doc();
716 let result = xptr_eval("element(main/0)", doc);
717 assert!(result.is_none());
718 }
719 }
720
721 #[test]
722 fn test_empty_expr() {
723 unsafe {
724 let doc = create_simple_doc();
725 let result = xptr_eval("", doc);
726 assert!(result.is_none());
727 }
728 }
729
730 #[test]
731 fn test_null_doc() {
732 unsafe {
733 let result = xptr_eval("main", ptr::null_mut());
734 assert!(result.is_none());
735 }
736 }
737
738 #[test]
739 fn test_xml_xptr_eval_c_abi() {
740 unsafe {
741 let doc = create_simple_doc();
742 let c_expr = CString::new("main").unwrap();
743 let node = xmlXPtrEval(c_expr.as_ptr(), doc);
744 assert!(!node.is_null());
745 c_name_eq!(node, "root");
746 }
747 }
748
749 #[test]
750 fn test_xml_xptr_eval_null_expr() {
751 unsafe {
752 let doc = create_simple_doc();
753 let node = xmlXPtrEval(ptr::null(), doc);
754 assert!(node.is_null());
755 }
756 }
757
758 #[test]
759 fn test_xml_xptr_eval_null_doc() {
760 unsafe {
761 let c_expr = CString::new("main").unwrap();
762 let node = xmlXPtrEval(c_expr.as_ptr(), ptr::null_mut());
763 assert!(node.is_null());
764 }
765 }
766
767 #[test]
768 fn test_xml_xptr_eval_node_set() {
769 unsafe {
770 let doc = create_simple_doc();
771 let c_expr = CString::new("main").unwrap();
772 let ns = xmlXPtrEvalNodeSet(c_expr.as_ptr(), doc);
773 assert!(!ns.is_null());
774 assert_eq!((*ns).nodeNr, 1);
775 assert!(!(*ns).nodeTab.is_null());
776 let node = *(*ns).nodeTab;
777 c_name_eq!(node, "root");
778 }
779 }
780
781 #[test]
782 fn test_element_scheme_not_found() {
783 unsafe {
784 let doc = create_complex_doc();
785 let result = xptr_eval("element(nonexistent)", doc);
786 assert!(result.is_none());
787 }
788 }
789
790 #[test]
791 fn test_element_scheme_extra_spaces() {
792 unsafe {
793 let doc = create_complex_doc();
794 let result = xptr_eval("element( main )", doc);
795 assert!(result.is_some());
796 c_name_eq!(result.unwrap(), "root");
797 }
798 }
799
800 #[test]
801 fn test_child3_no_id() {
802 unsafe {
803 let doc = create_complex_doc();
804 let result = xptr_eval("child3", doc);
805 assert!(result.is_none());
806 }
807 }
808}