1use crate::abi::structs::{_xmlAttr, _xmlDoc, _xmlNode};
18use crate::abi::types::xmlAttributeType::XML_ATTRIBUTE_ID;
19use crate::abi::types::xmlElementType::{XML_ELEMENT_NODE, XML_TEXT_NODE};
20use crate::xml::xpath::context::XPathContext;
21use crate::xml::xpath::types::NodeSet;
22use std::ffi::CStr;
23
24#[cfg(test)]
25use std::ffi::CString;
26use std::os::raw::c_char;
27use std::ptr;
28
29pub unsafe fn xptr_eval(expr: &str, doc: *mut _xmlDoc) -> Option<*mut _xmlNode> {
51 if doc.is_null() {
52 return None;
53 }
54
55 let expr = expr.trim();
56
57 if expr.is_empty() {
58 return None;
59 }
60
61 if let Some(result) = try_eval_scheme(expr, doc) {
63 return result;
64 }
65
66 shorthand_lookup(expr, doc)
68}
69
70pub unsafe fn xptr_eval_with_context(
79 expr: &str,
80 doc: *mut _xmlDoc,
81 _context: Option<&mut XPathContext>,
82) -> Option<*mut _xmlNode> {
83 xptr_eval(expr, doc)
84}
85
86pub unsafe extern "C" fn xmlXPtrEval(expr: *const c_char, doc: *mut _xmlDoc) -> *mut _xmlNode {
102 if expr.is_null() || doc.is_null() {
103 return ptr::null_mut();
104 }
105
106 let expr_str = match unsafe { CStr::from_ptr(expr) }.to_str() {
107 Ok(s) => s,
108 Err(_) => return ptr::null_mut(),
109 };
110
111 match unsafe { xptr_eval(expr_str, doc) } {
112 Some(node) => node,
113 None => ptr::null_mut(),
114 }
115}
116
117#[no_mangle]
126pub unsafe extern "C" fn xmlXPtrEvalNodeSet(
127 expr: *const c_char,
128 doc: *mut _xmlDoc,
129) -> *mut crate::abi::structs::_xmlNodeSet {
130 if expr.is_null() || doc.is_null() {
131 return ptr::null_mut();
132 }
133
134 let expr_str = match unsafe { CStr::from_ptr(expr) }.to_str() {
135 Ok(s) => s,
136 Err(_) => return ptr::null_mut(),
137 };
138
139 let node = unsafe { xptr_eval(expr_str, doc) };
140
141 let mut ns = NodeSet::new();
142 if let Some(n) = node {
143 ns.push(n);
144 }
145
146 unsafe { ns.to_raw() }
147}
148
149unsafe fn try_eval_scheme(expr: &str, doc: *mut _xmlDoc) -> Option<Option<*mut _xmlNode>> {
157 let expr = expr.trim();
158
159 if let Some(inner) = strip_scheme(expr, "element") {
161 return Some(unsafe { eval_element_scheme(inner, doc) });
162 }
163
164 None
167}
168
169fn strip_scheme<'a>(expr: &'a str, scheme: &str) -> Option<&'a str> {
174 let expr = expr.trim();
175
176 let expected_prefix = format!("{}(", scheme);
177 if !expr.starts_with(&expected_prefix) {
178 return None;
179 }
180
181 let inner_start = expected_prefix.len();
182 if !expr.ends_with(')') {
183 return None;
184 }
185
186 let inner_end = expr.len() - 1;
187 if inner_end <= inner_start {
188 return Some("");
189 }
190
191 Some(&expr[inner_start..inner_end])
192}
193
194unsafe fn eval_element_scheme(inner: &str, doc: *mut _xmlDoc) -> Option<*mut _xmlNode> {
207 let inner = inner.trim();
208 if inner.is_empty() {
209 return None;
210 }
211
212 let parts: Vec<&str> = inner.split('/').collect();
214 if parts.is_empty() {
215 return None;
216 }
217
218 let id = parts[0].trim();
219 if id.is_empty() {
220 return None;
221 }
222
223 let base = unsafe { find_element_by_id(id, doc) }?;
225
226 if parts.len() == 1 {
228 return Some(base);
229 }
230
231 let mut current = base;
233 for &part in &parts[1..] {
234 let index_str = part.trim();
235 let index: usize = match index_str.parse() {
236 Ok(n) if n >= 1 => n,
237 _ => return None,
238 };
239
240 current = unsafe { nth_child_element(current, index) }?;
242 }
243
244 Some(current)
245}
246
247unsafe fn nth_child_element(node: *mut _xmlNode, n: usize) -> Option<*mut _xmlNode> {
251 if node.is_null() {
252 return None;
253 }
254
255 let mut count = 0usize;
256 let mut child = unsafe { (*node).children };
257
258 while !child.is_null() {
259 let ty = unsafe { (*child).type_ };
260 if ty == XML_ELEMENT_NODE as std::os::raw::c_int {
261 count += 1;
262 if count == n {
263 return Some(child);
264 }
265 }
266 child = unsafe { (*child).next };
267 }
268
269 None
270}
271
272unsafe fn shorthand_lookup(name: &str, doc: *mut _xmlDoc) -> Option<*mut _xmlNode> {
281 unsafe { find_element_by_id(name, doc) }
282}
283
284unsafe fn find_element_by_id(id: &str, doc: *mut _xmlDoc) -> Option<*mut _xmlNode> {
305 if doc.is_null() || id.is_empty() {
306 return None;
307 }
308
309 let root = unsafe { (*doc).children };
312 if root.is_null() {
313 return None;
314 }
315
316 unsafe { walk_for_id(root, id) }
317}
318
319unsafe fn walk_for_id(node: *mut _xmlNode, id: &str) -> Option<*mut _xmlNode> {
321 if node.is_null() {
322 return None;
323 }
324
325 let ty = unsafe { (*node).type_ };
327 if ty == XML_ELEMENT_NODE as std::os::raw::c_int && unsafe { element_has_id(node, id) } {
328 return Some(node);
329 }
330
331 let mut child = unsafe { (*node).children };
333 while !child.is_null() {
334 if let Some(found) = unsafe { walk_for_id(child, id) } {
335 return Some(found);
336 }
337 child = unsafe { (*child).next };
338 }
339
340 None
341}
342
343unsafe fn element_has_id(node: *mut _xmlNode, id: &str) -> bool {
349 if node.is_null() {
350 return false;
351 }
352
353 let mut prop = unsafe { (*node).properties };
354 while !prop.is_null() {
355 let attr = unsafe { &*prop };
356
357 if attr.atype == XML_ATTRIBUTE_ID as std::os::raw::c_int {
359 if let Some(val) = unsafe { get_attr_value(prop) } {
360 if val == id {
361 return true;
362 }
363 }
364 }
365
366 if !attr.name.is_null() {
368 let name_str = unsafe { c_xmlchar_to_str(attr.name) };
369 if name_str.as_deref() == Some("id") || name_str.as_deref() == Some("ID") {
370 if let Some(val) = unsafe { get_attr_value(prop) } {
371 if val == id {
372 return true;
373 }
374 }
375 }
376 }
377
378 prop = unsafe { (*prop).next };
379 }
380
381 false
382}
383
384unsafe fn get_attr_value(attr: *mut _xmlAttr) -> Option<String> {
386 if attr.is_null() {
387 return None;
388 }
389
390 let children = unsafe { (*attr).children };
391 if children.is_null() {
392 return None;
393 }
394
395 let text = unsafe { &*children };
396 if text.type_ == XML_TEXT_NODE as std::os::raw::c_int && !text.content.is_null() {
397 let val = unsafe { c_xmlchar_to_str(text.content) };
398 return val;
399 }
400
401 None
402}
403
404unsafe fn c_xmlchar_to_str(ptr: *const crate::abi::types::xmlChar) -> Option<String> {
408 if ptr.is_null() {
409 return None;
410 }
411
412 let c_str = unsafe { CStr::from_ptr(ptr as *const c_char) };
414 match c_str.to_str() {
415 Ok(s) => Some(s.to_string()),
416 Err(_) => None,
417 }
418}
419
420#[cfg(test)]
425mod tests {
426 use super::*;
427 use crate::abi::allocator::xmlMallocZero;
428 use crate::abi::types::xmlElementType::*;
429 use std::mem;
430 use std::os::raw::c_int;
431 use std::ptr;
432
433 unsafe fn create_simple_doc() -> *mut _xmlDoc {
437 let doc = xmlMallocZero(mem::size_of::<_xmlDoc>()) as *mut _xmlDoc;
438 assert!(!doc.is_null());
439
440 let root = xmlMallocZero(mem::size_of::<_xmlNode>()) as *mut _xmlNode;
441 assert!(!root.is_null());
442
443 unsafe {
444 (*doc).type_ = XML_DOCUMENT_NODE as c_int;
445 (*doc).doc = doc;
446 (*doc).children = root;
447
448 (*root).type_ = XML_ELEMENT_NODE as c_int;
449 (*root).name = string_to_xmlchar("root");
450 (*root).parent = doc as *mut _xmlNode;
451 (*root).doc = doc;
452 (*root).properties = ptr::null_mut();
453 }
454
455 let attr = unsafe { add_id_attr(root, "id", "main") };
457 unsafe {
458 (*root).properties = attr;
459 }
460
461 doc
462 }
463
464 unsafe fn create_complex_doc() -> *mut _xmlDoc {
475 let doc = xmlMallocZero(mem::size_of::<_xmlDoc>()) as *mut _xmlDoc;
476 assert!(!doc.is_null());
477
478 let root = xmlMallocZero(mem::size_of::<_xmlNode>()) as *mut _xmlNode;
480 assert!(!root.is_null());
481
482 unsafe {
483 (*doc).type_ = XML_DOCUMENT_NODE as c_int;
484 (*doc).doc = doc;
485 (*doc).children = root;
486
487 (*root).type_ = XML_ELEMENT_NODE as c_int;
488 (*root).name = string_to_xmlchar("root");
489 (*root).parent = doc as *mut _xmlNode;
490 (*root).doc = doc;
491 }
492
493 let attr_root = unsafe { add_id_attr(root, "id", "main") };
494 unsafe { (*root).properties = attr_root };
495
496 let child1 = unsafe { append_child_element(root, "child1") };
498 let attr_c1 = unsafe { add_id_attr(child1, "id", "a") };
499 unsafe { (*child1).properties = attr_c1 };
500
501 let child2 = unsafe { append_child_element(root, "child2") };
503 let attr_c2 = unsafe { add_id_attr(child2, "id", "b") };
504 unsafe { (*child2).properties = attr_c2 };
505
506 let grandchild = unsafe { append_child_element(child2, "grandchild") };
508 let attr_gc = unsafe { add_id_attr(grandchild, "id", "c") };
509 unsafe { (*grandchild).properties = attr_gc };
510
511 let _child3 = unsafe { append_child_element(root, "child3") };
513
514 doc
515 }
516
517 unsafe fn string_to_xmlchar(s: &str) -> *const crate::abi::types::xmlChar {
518 let c_str = CString::new(s).unwrap();
519 c_str.into_raw() as *const crate::abi::types::xmlChar
520 }
521
522 unsafe fn append_child_element(parent: *mut _xmlNode, name: &str) -> *mut _xmlNode {
523 let node = xmlMallocZero(mem::size_of::<_xmlNode>()) as *mut _xmlNode;
524 assert!(!node.is_null());
525
526 unsafe {
527 (*node).type_ = XML_ELEMENT_NODE as c_int;
528 (*node).name = string_to_xmlchar(name);
529 (*node).parent = parent;
530 (*node).doc = (*parent).doc;
531 (*node).next = ptr::null_mut();
532 (*node).prev = (*parent).last;
533 (*node).properties = ptr::null_mut();
534
535 if (*parent).children.is_null() {
537 (*parent).children = node;
538 (*parent).last = node;
539 } else {
540 let last = (*parent).last;
541 if !last.is_null() {
542 (*last).next = node;
543 }
544 (*parent).last = node;
545 }
546 }
547
548 node
549 }
550
551 unsafe fn add_id_attr(node: *mut _xmlNode, name: &str, value: &str) -> *mut _xmlAttr {
552 let attr = xmlMallocZero(mem::size_of::<_xmlAttr>()) as *mut _xmlAttr;
553 assert!(!attr.is_null());
554
555 let text = xmlMallocZero(mem::size_of::<_xmlNode>()) as *mut _xmlNode;
557 assert!(!text.is_null());
558
559 unsafe {
560 (*attr).type_ = 2; (*attr).name = string_to_xmlchar(name);
562 (*attr).parent = node;
563 (*attr).doc = (*node).doc;
564 (*attr).children = text;
565 (*attr).last = text;
566 (*attr).atype = crate::abi::types::xmlAttributeType::XML_ATTRIBUTE_CDATA as c_int;
567 (*attr).next = ptr::null_mut();
568 (*attr).prev = ptr::null_mut();
569
570 (*text).type_ = XML_TEXT_NODE as c_int;
571 (*text).name = string_to_xmlchar("text");
572 (*text).content = string_to_xmlchar(value) as *mut crate::abi::types::xmlChar;
573 (*text).parent = attr as *mut _xmlNode;
574 (*text).doc = (*node).doc;
575 (*text).next = ptr::null_mut();
576 (*text).prev = ptr::null_mut();
577 }
578
579 attr
580 }
581
582 macro_rules! c_name_eq {
585 ($node:expr, $expected:expr) => {
586 assert_eq!(
587 CStr::from_ptr((*$node).name as *const c_char)
588 .to_str()
589 .unwrap(),
590 $expected
591 );
592 };
593 }
594
595 #[test]
596 fn test_shorthand_pointer() {
597 unsafe {
598 let doc = create_simple_doc();
599 let result = xptr_eval("main", doc);
600 assert!(result.is_some());
601 c_name_eq!(result.unwrap(), "root");
602 }
603 }
604
605 #[test]
606 fn test_shorthand_pointer_not_found() {
607 unsafe {
608 let doc = create_simple_doc();
609 let result = xptr_eval("nonexistent", doc);
610 assert!(result.is_none());
611 }
612 }
613
614 #[test]
615 fn test_element_scheme_basic() {
616 unsafe {
617 let doc = create_complex_doc();
618
619 let result = xptr_eval("element(main)", doc);
620 assert!(result.is_some());
621 c_name_eq!(result.unwrap(), "root");
622
623 let result = xptr_eval("element(a)", doc);
624 assert!(result.is_some());
625 c_name_eq!(result.unwrap(), "child1");
626
627 let result = xptr_eval("element(c)", doc);
628 assert!(result.is_some());
629 c_name_eq!(result.unwrap(), "grandchild");
630 }
631 }
632
633 #[test]
634 fn test_element_scheme_with_child_sequence() {
635 unsafe {
636 let doc = create_complex_doc();
637
638 let result = xptr_eval("element(main/1)", doc);
639 assert!(result.is_some());
640 c_name_eq!(result.unwrap(), "child1");
641
642 let result = xptr_eval("element(main/2)", doc);
643 assert!(result.is_some());
644 c_name_eq!(result.unwrap(), "child2");
645
646 let result = xptr_eval("element(main/2/1)", doc);
647 assert!(result.is_some());
648 c_name_eq!(result.unwrap(), "grandchild");
649 }
650 }
651
652 #[test]
653 fn test_element_scheme_child_out_of_range() {
654 unsafe {
655 let doc = create_complex_doc();
656 let result = xptr_eval("element(main/99)", doc);
657 assert!(result.is_none());
658 }
659 }
660
661 #[test]
662 fn test_element_scheme_zero_index() {
663 unsafe {
664 let doc = create_complex_doc();
665 let result = xptr_eval("element(main/0)", doc);
666 assert!(result.is_none());
667 }
668 }
669
670 #[test]
671 fn test_empty_expr() {
672 unsafe {
673 let doc = create_simple_doc();
674 let result = xptr_eval("", doc);
675 assert!(result.is_none());
676 }
677 }
678
679 #[test]
680 fn test_null_doc() {
681 unsafe {
682 let result = xptr_eval("main", ptr::null_mut());
683 assert!(result.is_none());
684 }
685 }
686
687 #[test]
688 fn test_xml_xptr_eval_c_abi() {
689 unsafe {
690 let doc = create_simple_doc();
691 let c_expr = CString::new("main").unwrap();
692 let node = xmlXPtrEval(c_expr.as_ptr(), doc);
693 assert!(!node.is_null());
694 c_name_eq!(node, "root");
695 }
696 }
697
698 #[test]
699 fn test_xml_xptr_eval_null_expr() {
700 unsafe {
701 let doc = create_simple_doc();
702 let node = xmlXPtrEval(ptr::null(), doc);
703 assert!(node.is_null());
704 }
705 }
706
707 #[test]
708 fn test_xml_xptr_eval_null_doc() {
709 unsafe {
710 let c_expr = CString::new("main").unwrap();
711 let node = xmlXPtrEval(c_expr.as_ptr(), ptr::null_mut());
712 assert!(node.is_null());
713 }
714 }
715
716 #[test]
717 fn test_xml_xptr_eval_node_set() {
718 unsafe {
719 let doc = create_simple_doc();
720 let c_expr = CString::new("main").unwrap();
721 let ns = xmlXPtrEvalNodeSet(c_expr.as_ptr(), doc);
722 assert!(!ns.is_null());
723 assert_eq!((*ns).nodeNr, 1);
724 assert!(!(*ns).nodeTab.is_null());
725 let node = *(*ns).nodeTab;
726 c_name_eq!(node, "root");
727 }
728 }
729
730 #[test]
731 fn test_element_scheme_not_found() {
732 unsafe {
733 let doc = create_complex_doc();
734 let result = xptr_eval("element(nonexistent)", doc);
735 assert!(result.is_none());
736 }
737 }
738
739 #[test]
740 fn test_element_scheme_extra_spaces() {
741 unsafe {
742 let doc = create_complex_doc();
743 let result = xptr_eval("element( main )", doc);
744 assert!(result.is_some());
745 c_name_eq!(result.unwrap(), "root");
746 }
747 }
748
749 #[test]
750 fn test_child3_no_id() {
751 unsafe {
752 let doc = create_complex_doc();
753 let result = xptr_eval("child3", doc);
754 assert!(result.is_none());
755 }
756 }
757}