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 {
328 if unsafe { element_has_id(node, id) } {
329 return Some(node);
330 }
331 }
332
333 let mut child = unsafe { (*node).children };
335 while !child.is_null() {
336 if let Some(found) = unsafe { walk_for_id(child, id) } {
337 return Some(found);
338 }
339 child = unsafe { (*child).next };
340 }
341
342 None
343}
344
345unsafe fn element_has_id(node: *mut _xmlNode, id: &str) -> bool {
351 if node.is_null() {
352 return false;
353 }
354
355 let mut prop = unsafe { (*node).properties };
356 while !prop.is_null() {
357 let attr = unsafe { &*prop };
358
359 if attr.atype == XML_ATTRIBUTE_ID as std::os::raw::c_int {
361 if let Some(val) = unsafe { get_attr_value(prop) } {
362 if val == id {
363 return true;
364 }
365 }
366 }
367
368 if !attr.name.is_null() {
370 let name_str = unsafe { c_xmlchar_to_str(attr.name) };
371 if name_str.as_deref() == Some("id") || name_str.as_deref() == Some("ID") {
372 if let Some(val) = unsafe { get_attr_value(prop) } {
373 if val == id {
374 return true;
375 }
376 }
377 }
378 }
379
380 prop = unsafe { (*prop).next };
381 }
382
383 false
384}
385
386unsafe fn get_attr_value(attr: *mut _xmlAttr) -> Option<String> {
388 if attr.is_null() {
389 return None;
390 }
391
392 let children = unsafe { (*attr).children };
393 if children.is_null() {
394 return None;
395 }
396
397 let text = unsafe { &*children };
398 if text.type_ == XML_TEXT_NODE as std::os::raw::c_int && !text.content.is_null() {
399 let val = unsafe { c_xmlchar_to_str(text.content) };
400 return val;
401 }
402
403 None
404}
405
406unsafe fn c_xmlchar_to_str(ptr: *const crate::abi::types::xmlChar) -> Option<String> {
410 if ptr.is_null() {
411 return None;
412 }
413
414 let c_str = unsafe { CStr::from_ptr(ptr as *const c_char) };
416 match c_str.to_str() {
417 Ok(s) => Some(s.to_string()),
418 Err(_) => None,
419 }
420}
421
422#[cfg(test)]
427mod tests {
428 use super::*;
429 use crate::abi::allocator::xmlMallocZero;
430 use crate::abi::types::xmlElementType::*;
431 use std::mem;
432 use std::os::raw::c_int;
433 use std::ptr;
434
435 unsafe fn create_simple_doc() -> *mut _xmlDoc {
439 let doc = xmlMallocZero(mem::size_of::<_xmlDoc>()) as *mut _xmlDoc;
440 assert!(!doc.is_null());
441
442 let root = xmlMallocZero(mem::size_of::<_xmlNode>()) as *mut _xmlNode;
443 assert!(!root.is_null());
444
445 unsafe {
446 (*doc).type_ = XML_DOCUMENT_NODE as c_int;
447 (*doc).doc = doc;
448 (*doc).children = root;
449
450 (*root).type_ = XML_ELEMENT_NODE as c_int;
451 (*root).name = string_to_xmlchar("root");
452 (*root).parent = doc as *mut _xmlNode;
453 (*root).doc = doc;
454 (*root).properties = ptr::null_mut();
455 }
456
457 let attr = unsafe { add_id_attr(root, "id", "main") };
459 unsafe {
460 (*root).properties = attr;
461 }
462
463 doc
464 }
465
466 unsafe fn create_complex_doc() -> *mut _xmlDoc {
477 let doc = xmlMallocZero(mem::size_of::<_xmlDoc>()) as *mut _xmlDoc;
478 assert!(!doc.is_null());
479
480 let root = xmlMallocZero(mem::size_of::<_xmlNode>()) as *mut _xmlNode;
482 assert!(!root.is_null());
483
484 unsafe {
485 (*doc).type_ = XML_DOCUMENT_NODE as c_int;
486 (*doc).doc = doc;
487 (*doc).children = root;
488
489 (*root).type_ = XML_ELEMENT_NODE as c_int;
490 (*root).name = string_to_xmlchar("root");
491 (*root).parent = doc as *mut _xmlNode;
492 (*root).doc = doc;
493 }
494
495 let attr_root = unsafe { add_id_attr(root, "id", "main") };
496 unsafe { (*root).properties = attr_root };
497
498 let child1 = unsafe { append_child_element(root, "child1") };
500 let attr_c1 = unsafe { add_id_attr(child1, "id", "a") };
501 unsafe { (*child1).properties = attr_c1 };
502
503 let child2 = unsafe { append_child_element(root, "child2") };
505 let attr_c2 = unsafe { add_id_attr(child2, "id", "b") };
506 unsafe { (*child2).properties = attr_c2 };
507
508 let grandchild = unsafe { append_child_element(child2, "grandchild") };
510 let attr_gc = unsafe { add_id_attr(grandchild, "id", "c") };
511 unsafe { (*grandchild).properties = attr_gc };
512
513 let _child3 = unsafe { append_child_element(root, "child3") };
515
516 doc
517 }
518
519 unsafe fn string_to_xmlchar(s: &str) -> *const crate::abi::types::xmlChar {
520 let c_str = CString::new(s).unwrap();
521 c_str.into_raw() as *const crate::abi::types::xmlChar
522 }
523
524 unsafe fn append_child_element(parent: *mut _xmlNode, name: &str) -> *mut _xmlNode {
525 let node = xmlMallocZero(mem::size_of::<_xmlNode>()) as *mut _xmlNode;
526 assert!(!node.is_null());
527
528 unsafe {
529 (*node).type_ = XML_ELEMENT_NODE as c_int;
530 (*node).name = string_to_xmlchar(name);
531 (*node).parent = parent;
532 (*node).doc = (*parent).doc;
533 (*node).next = ptr::null_mut();
534 (*node).prev = (*parent).last;
535 (*node).properties = ptr::null_mut();
536
537 if (*parent).children.is_null() {
539 (*parent).children = node;
540 (*parent).last = node;
541 } else {
542 let last = (*parent).last;
543 if !last.is_null() {
544 (*last).next = node;
545 }
546 (*parent).last = node;
547 }
548 }
549
550 node
551 }
552
553 unsafe fn add_id_attr(node: *mut _xmlNode, name: &str, value: &str) -> *mut _xmlAttr {
554 let attr = xmlMallocZero(mem::size_of::<_xmlAttr>()) as *mut _xmlAttr;
555 assert!(!attr.is_null());
556
557 let text = xmlMallocZero(mem::size_of::<_xmlNode>()) as *mut _xmlNode;
559 assert!(!text.is_null());
560
561 unsafe {
562 (*attr).type_ = 2; (*attr).name = string_to_xmlchar(name);
564 (*attr).parent = node;
565 (*attr).doc = (*node).doc;
566 (*attr).children = text;
567 (*attr).last = text;
568 (*attr).atype = crate::abi::types::xmlAttributeType::XML_ATTRIBUTE_CDATA as c_int;
569 (*attr).next = ptr::null_mut();
570 (*attr).prev = ptr::null_mut();
571
572 (*text).type_ = XML_TEXT_NODE as c_int;
573 (*text).name = string_to_xmlchar("text");
574 (*text).content = string_to_xmlchar(value) as *mut crate::abi::types::xmlChar;
575 (*text).parent = attr as *mut _xmlNode;
576 (*text).doc = (*node).doc;
577 (*text).next = ptr::null_mut();
578 (*text).prev = ptr::null_mut();
579 }
580
581 attr
582 }
583
584 macro_rules! c_name_eq {
587 ($node:expr, $expected:expr) => {
588 assert_eq!(
589 CStr::from_ptr((*$node).name as *const c_char)
590 .to_str()
591 .unwrap(),
592 $expected
593 );
594 };
595 }
596
597 #[test]
598 fn test_shorthand_pointer() {
599 unsafe {
600 let doc = create_simple_doc();
601 let result = xptr_eval("main", doc);
602 assert!(result.is_some());
603 c_name_eq!(result.unwrap(), "root");
604 }
605 }
606
607 #[test]
608 fn test_shorthand_pointer_not_found() {
609 unsafe {
610 let doc = create_simple_doc();
611 let result = xptr_eval("nonexistent", doc);
612 assert!(result.is_none());
613 }
614 }
615
616 #[test]
617 fn test_element_scheme_basic() {
618 unsafe {
619 let doc = create_complex_doc();
620
621 let result = xptr_eval("element(main)", doc);
622 assert!(result.is_some());
623 c_name_eq!(result.unwrap(), "root");
624
625 let result = xptr_eval("element(a)", doc);
626 assert!(result.is_some());
627 c_name_eq!(result.unwrap(), "child1");
628
629 let result = xptr_eval("element(c)", doc);
630 assert!(result.is_some());
631 c_name_eq!(result.unwrap(), "grandchild");
632 }
633 }
634
635 #[test]
636 fn test_element_scheme_with_child_sequence() {
637 unsafe {
638 let doc = create_complex_doc();
639
640 let result = xptr_eval("element(main/1)", doc);
641 assert!(result.is_some());
642 c_name_eq!(result.unwrap(), "child1");
643
644 let result = xptr_eval("element(main/2)", doc);
645 assert!(result.is_some());
646 c_name_eq!(result.unwrap(), "child2");
647
648 let result = xptr_eval("element(main/2/1)", doc);
649 assert!(result.is_some());
650 c_name_eq!(result.unwrap(), "grandchild");
651 }
652 }
653
654 #[test]
655 fn test_element_scheme_child_out_of_range() {
656 unsafe {
657 let doc = create_complex_doc();
658 let result = xptr_eval("element(main/99)", doc);
659 assert!(result.is_none());
660 }
661 }
662
663 #[test]
664 fn test_element_scheme_zero_index() {
665 unsafe {
666 let doc = create_complex_doc();
667 let result = xptr_eval("element(main/0)", doc);
668 assert!(result.is_none());
669 }
670 }
671
672 #[test]
673 fn test_empty_expr() {
674 unsafe {
675 let doc = create_simple_doc();
676 let result = xptr_eval("", doc);
677 assert!(result.is_none());
678 }
679 }
680
681 #[test]
682 fn test_null_doc() {
683 unsafe {
684 let result = xptr_eval("main", ptr::null_mut());
685 assert!(result.is_none());
686 }
687 }
688
689 #[test]
690 fn test_xml_xptr_eval_c_abi() {
691 unsafe {
692 let doc = create_simple_doc();
693 let c_expr = CString::new("main").unwrap();
694 let node = xmlXPtrEval(c_expr.as_ptr(), doc);
695 assert!(!node.is_null());
696 c_name_eq!(node, "root");
697 }
698 }
699
700 #[test]
701 fn test_xml_xptr_eval_null_expr() {
702 unsafe {
703 let doc = create_simple_doc();
704 let node = xmlXPtrEval(ptr::null(), doc);
705 assert!(node.is_null());
706 }
707 }
708
709 #[test]
710 fn test_xml_xptr_eval_null_doc() {
711 unsafe {
712 let c_expr = CString::new("main").unwrap();
713 let node = xmlXPtrEval(c_expr.as_ptr(), ptr::null_mut());
714 assert!(node.is_null());
715 }
716 }
717
718 #[test]
719 fn test_xml_xptr_eval_node_set() {
720 unsafe {
721 let doc = create_simple_doc();
722 let c_expr = CString::new("main").unwrap();
723 let ns = xmlXPtrEvalNodeSet(c_expr.as_ptr(), doc);
724 assert!(!ns.is_null());
725 assert_eq!((*ns).nodeNr, 1);
726 assert!(!(*ns).nodeTab.is_null());
727 let node = *(*ns).nodeTab;
728 c_name_eq!(node, "root");
729 }
730 }
731
732 #[test]
733 fn test_element_scheme_not_found() {
734 unsafe {
735 let doc = create_complex_doc();
736 let result = xptr_eval("element(nonexistent)", doc);
737 assert!(result.is_none());
738 }
739 }
740
741 #[test]
742 fn test_element_scheme_extra_spaces() {
743 unsafe {
744 let doc = create_complex_doc();
745 let result = xptr_eval("element( main )", doc);
746 assert!(result.is_some());
747 c_name_eq!(result.unwrap(), "root");
748 }
749 }
750
751 #[test]
752 fn test_child3_no_id() {
753 unsafe {
754 let doc = create_complex_doc();
755 let result = xptr_eval("child3", doc);
756 assert!(result.is_none());
757 }
758 }
759}