1use crate::abi::structs::{_xmlDoc, _xmlNode, _xmlXPathObject};
18use crate::abi::types::xmlChar;
19use crate::xml::xpath::types::XPathValue;
20use std::collections::HashMap;
21use std::os::raw::c_void;
22
23pub type XPathFunction = fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String>;
33
34pub type VarLookupFunc =
47 unsafe extern "C" fn(*mut c_void, *const xmlChar, *const xmlChar) -> *mut _xmlXPathObject;
48
49pub type FuncLookupFunc =
62 unsafe extern "C" fn(*mut c_void, *const xmlChar, *const xmlChar) -> *mut c_void;
63
64#[derive(Debug, Clone)]
86pub struct XPathContext {
87 pub document: *mut _xmlDoc,
89
90 pub context_node: *mut _xmlNode,
92
93 pub context_position: i32,
95
96 pub context_size: i32,
98
99 pub variables: HashMap<String, XPathValue>,
101
102 pub namespaces: HashMap<String, String>,
104
105 pub functions: HashMap<String, XPathFunction>,
107
108 pub error: Option<String>,
110
111 pub proximity_position: i32,
113
114 pub context_list: Vec<*mut _xmlNode>,
116
117 pub recursion_depth: u32,
119
120 pub var_lookup_func: Option<VarLookupFunc>,
122
123 pub var_lookup_data: *mut c_void,
125
126 pub func_lookup_func: Option<FuncLookupFunc>,
128
129 pub func_lookup_data: *mut c_void,
131}
132
133impl XPathContext {
134 pub fn new(doc: *mut _xmlDoc) -> Self {
148 Self {
149 document: doc,
150 context_node: std::ptr::null_mut(),
151 context_position: 1,
152 context_size: 1,
153 variables: HashMap::new(),
154 namespaces: HashMap::new(),
155 functions: HashMap::new(),
156 error: None,
157 proximity_position: 1,
158 context_list: Vec::new(),
159 recursion_depth: 0,
160 var_lookup_func: None,
161 var_lookup_data: std::ptr::null_mut(),
162 func_lookup_func: None,
163 func_lookup_data: std::ptr::null_mut(),
164 }
165 }
166
167 pub fn set_context_node(&mut self, node: *mut _xmlNode) {
176 self.context_node = node;
177 if node.is_null() {
178 self.context_list.clear();
179 self.context_position = 1;
180 self.context_size = 1;
181 self.proximity_position = 1;
182 } else {
183 self.context_list = vec![node];
184 self.context_position = 1;
185 self.context_size = 1;
186 self.proximity_position = 1;
187 }
188 }
189
190 pub fn set_context_list(&mut self, nodes: Vec<*mut _xmlNode>) {
198 self.context_size = nodes.len() as i32;
199 self.context_list = nodes;
200 self.context_position = 1;
201 self.proximity_position = 1;
202 }
203
204 pub fn resolve_variable(&self, name: &str) -> Option<XPathValue> {
220 if let Some(value) = self.variables.get(name) {
222 return Some(value.clone());
223 }
224
225 if let Some(lookup) = self.var_lookup_func {
227 let c_name: Vec<xmlChar> = name.bytes().collect();
229 let result = unsafe { lookup(self.var_lookup_data, std::ptr::null(), c_name.as_ptr()) };
232 if !result.is_null() {
233 unsafe {
238 let _ = result; }
243 }
244 }
245
246 None
247 }
248
249 pub fn resolve_namespace(&self, prefix: &str) -> Option<String> {
257 if let Some(uri) = self.namespaces.get(prefix) {
259 return Some(uri.clone());
260 }
261
262 let mut current = self.context_node;
265 while !current.is_null() {
266 unsafe {
269 let mut ns = (*current).nsDef;
270 while !ns.is_null() {
271 let ns_prefix = (*ns).prefix;
272 let ns_href = (*ns).href;
273
274 let prefix_matches = if ns_prefix.is_null() {
276 prefix.is_empty()
279 } else {
280 let mut len = 0;
282 while *ns_prefix.add(len) != 0 {
283 len += 1;
284 }
285 let slice = std::slice::from_raw_parts(ns_prefix, len);
286 slice == prefix.as_bytes()
287 };
288
289 if prefix_matches {
290 let mut len = 0;
292 while *ns_href.add(len) != 0 {
293 len += 1;
294 }
295 let slice = std::slice::from_raw_parts(ns_href, len);
296 return Some(String::from_utf8_lossy(slice).into_owned());
297 }
298
299 ns = (*ns).next;
300 }
301 }
302
303 unsafe {
306 current = (*current).parent;
307 }
308 }
309
310 None
311 }
312
313 pub fn lookup_function(&self, name: &str) -> Option<XPathFunction> {
320 if let Some(func) = self.functions.get(name) {
322 return Some(*func);
323 }
324
325 if let Some(lookup) = self.func_lookup_func {
327 let c_name: Vec<xmlChar> = name.bytes().collect();
328 let _result =
330 unsafe { lookup(self.func_lookup_data, std::ptr::null(), c_name.as_ptr()) };
331 }
335
336 None
337 }
338
339 pub fn register_function(&mut self, name: &str, func: XPathFunction) {
345 self.functions.insert(name.to_string(), func);
346 }
347
348 pub fn register_variable(&mut self, name: &str, value: XPathValue) {
354 self.variables.insert(name.to_string(), value);
355 }
356
357 pub fn register_namespace(&mut self, prefix: &str, uri: &str) {
362 self.namespaces.insert(prefix.to_string(), uri.to_string());
363 }
364
365 pub fn set_error(&mut self, msg: &str) {
369 self.error = Some(msg.to_string());
370 }
371
372 pub fn clear_error(&mut self) {
374 self.error = None;
375 }
376
377 pub fn push_recursion(&mut self) -> Result<(), String> {
385 const MAX_RECURSION_DEPTH: u32 = 1000;
386 if self.recursion_depth >= MAX_RECURSION_DEPTH {
387 return Err(
388 "XPath evaluation recursion depth exceeded (infinite recursion?)".to_string(),
389 );
390 }
391 self.recursion_depth += 1;
392 Ok(())
393 }
394
395 pub fn pop_recursion(&mut self) {
405 assert!(
406 self.recursion_depth > 0,
407 "unbalanced pop_recursion: recursion_depth is already 0"
408 );
409 self.recursion_depth -= 1;
410 }
411
412 pub fn has_context_node(&self) -> bool {
414 !self.context_node.is_null()
415 }
416
417 pub fn reset(&mut self) {
422 self.context_node = std::ptr::null_mut();
423 self.context_position = 1;
424 self.context_size = 1;
425 self.error = None;
426 self.proximity_position = 1;
427 self.context_list.clear();
428 self.recursion_depth = 0;
429 }
430
431 pub fn position(&self) -> i32 {
435 self.proximity_position
436 }
437
438 pub fn last(&self) -> i32 {
442 self.context_size
443 }
444
445 pub fn advance_position(&mut self) {
450 self.proximity_position += 1;
451 self.context_position = self.proximity_position;
452 }
453
454 pub fn reset_position(&mut self) {
456 self.proximity_position = 1;
457 self.context_position = 1;
458 }
459}
460
461impl Default for XPathContext {
462 fn default() -> Self {
467 Self::new(std::ptr::null_mut())
468 }
469}
470
471#[cfg(test)]
476mod tests {
477 use super::*;
478 use crate::xml::xpath::ast::Expr;
479 use crate::xml::xpath::types::NodeSet;
480
481 unsafe fn create_test_doc() -> *mut _xmlDoc {
487 let layout = std::alloc::Layout::new::<_xmlDoc>();
489 let ptr = std::alloc::alloc_zeroed(layout) as *mut _xmlDoc;
490 assert!(!ptr.is_null(), "failed to allocate test document");
491 ptr
492 }
493
494 unsafe fn create_test_node() -> *mut _xmlNode {
498 let layout = std::alloc::Layout::new::<_xmlNode>();
499 let ptr = std::alloc::alloc_zeroed(layout) as *mut _xmlNode;
500 assert!(!ptr.is_null(), "failed to allocate test node");
501 ptr
502 }
503
504 unsafe fn free_test_doc(doc: *mut _xmlDoc) {
506 if !doc.is_null() {
507 let layout = std::alloc::Layout::new::<_xmlDoc>();
508 std::alloc::dealloc(doc as *mut u8, layout);
509 }
510 }
511
512 unsafe fn free_test_node(node: *mut _xmlNode) {
514 if !node.is_null() {
515 let layout = std::alloc::Layout::new::<_xmlNode>();
516 std::alloc::dealloc(node as *mut u8, layout);
517 }
518 }
519
520 #[test]
523 fn test_new_context() {
524 let ctx = XPathContext::new(std::ptr::null_mut());
525 assert!(ctx.document.is_null());
526 assert!(ctx.context_node.is_null());
527 assert_eq!(ctx.context_position, 1);
528 assert_eq!(ctx.context_size, 1);
529 assert!(ctx.variables.is_empty());
530 assert!(ctx.namespaces.is_empty());
531 assert!(ctx.functions.is_empty());
532 assert!(ctx.error.is_none());
533 assert_eq!(ctx.proximity_position, 1);
534 assert!(ctx.context_list.is_empty());
535 assert_eq!(ctx.recursion_depth, 0);
536 assert!(ctx.var_lookup_func.is_none());
537 assert!(ctx.var_lookup_data.is_null());
538 assert!(ctx.func_lookup_func.is_none());
539 assert!(ctx.func_lookup_data.is_null());
540 }
541
542 #[test]
543 fn test_default_context() {
544 let ctx = XPathContext::default();
545 assert!(ctx.document.is_null());
546 assert_eq!(ctx.context_position, 1);
547 }
548
549 #[test]
550 fn test_new_with_doc() {
551 unsafe {
552 let doc = create_test_doc();
553 let ctx = XPathContext::new(doc);
554 assert_eq!(ctx.document, doc);
555 free_test_doc(doc);
556 }
557 }
558
559 #[test]
562 fn test_set_context_node_non_null() {
563 unsafe {
564 let node = create_test_node();
565 let mut ctx = XPathContext::new(std::ptr::null_mut());
566 ctx.set_context_node(node);
567
568 assert_eq!(ctx.context_node, node);
569 assert_eq!(ctx.context_position, 1);
570 assert_eq!(ctx.context_size, 1);
571 assert_eq!(ctx.proximity_position, 1);
572 assert_eq!(ctx.context_list.len(), 1);
573 assert_eq!(ctx.context_list[0], node);
574
575 free_test_node(node);
576 }
577 }
578
579 #[test]
580 fn test_set_context_node_null() {
581 let mut ctx = XPathContext::new(std::ptr::null_mut());
582 let sentinel = 1 as *mut _xmlNode;
585 ctx.context_list = vec![sentinel];
586 ctx.context_position = 5;
587 ctx.context_size = 5;
588 ctx.proximity_position = 5;
589
590 ctx.set_context_node(std::ptr::null_mut());
592 assert!(ctx.context_node.is_null());
593 assert!(ctx.context_list.is_empty());
594 assert_eq!(ctx.context_position, 1);
595 assert_eq!(ctx.context_size, 1);
596 assert_eq!(ctx.proximity_position, 1);
597 }
598
599 #[test]
602 fn test_set_context_list() {
603 unsafe {
604 let node1 = create_test_node();
605 let node2 = create_test_node();
606 let nodes = vec![node1, node2];
607
608 let mut ctx = XPathContext::new(std::ptr::null_mut());
609 ctx.set_context_list(nodes.clone());
610
611 assert_eq!(ctx.context_list.len(), 2);
612 assert_eq!(ctx.context_size, 2);
613 assert_eq!(ctx.context_position, 1);
614 assert_eq!(ctx.proximity_position, 1);
615
616 free_test_node(node1);
617 free_test_node(node2);
618 }
619 }
620
621 #[test]
622 fn test_set_context_list_empty() {
623 let mut ctx = XPathContext::new(std::ptr::null_mut());
624 ctx.set_context_list(vec![]);
625
626 assert!(ctx.context_list.is_empty());
627 assert_eq!(ctx.context_size, 0);
628 assert_eq!(ctx.context_position, 1);
629 }
630
631 #[test]
634 fn test_register_and_resolve_variable() {
635 let mut ctx = XPathContext::new(std::ptr::null_mut());
636 ctx.register_variable("foo", XPathValue::String("bar".to_string()));
637
638 let result = ctx.resolve_variable("foo");
639 assert!(result.is_some());
640 assert_eq!(result.unwrap().as_string(), "bar");
641 }
642
643 #[test]
644 fn test_resolve_unknown_variable() {
645 let ctx = XPathContext::new(std::ptr::null_mut());
646 assert!(ctx.resolve_variable("nonexistent").is_none());
647 }
648
649 #[test]
650 fn test_register_variable_number() {
651 let mut ctx = XPathContext::new(std::ptr::null_mut());
652 ctx.register_variable("pi", XPathValue::Number(3.14159));
653
654 let result = ctx.resolve_variable("pi");
655 assert!(result.is_some());
656 let val = result.unwrap();
657 assert!((val.as_number() - 3.14159).abs() < 1e-10);
658 }
659
660 #[test]
661 fn test_register_variable_boolean() {
662 let mut ctx = XPathContext::new(std::ptr::null_mut());
663 ctx.register_variable("flag", XPathValue::Boolean(true));
664
665 let result = ctx.resolve_variable("flag");
666 assert!(result.is_some());
667 assert!(result.unwrap().as_boolean());
668 }
669
670 #[test]
671 fn test_register_variable_nodeset() {
672 let mut ctx = XPathContext::new(std::ptr::null_mut());
673 let ns = NodeSet::new();
674 ctx.register_variable("nodes", XPathValue::NodeSet(ns));
675
676 let result = ctx.resolve_variable("nodes");
677 assert!(result.is_some());
678 assert!(matches!(result.unwrap(), XPathValue::NodeSet(_)));
679 }
680
681 #[test]
682 fn test_variable_overwrite() {
683 let mut ctx = XPathContext::new(std::ptr::null_mut());
684 ctx.register_variable("x", XPathValue::Number(1.0));
685 ctx.register_variable("x", XPathValue::Number(2.0));
686
687 let result = ctx.resolve_variable("x");
688 assert!(result.is_some());
689 assert!((result.unwrap().as_number() - 2.0).abs() < 1e-10);
690 }
691
692 #[test]
695 fn test_register_and_resolve_namespace() {
696 let mut ctx = XPathContext::new(std::ptr::null_mut());
697 ctx.register_namespace("xslt", "http://www.w3.org/1999/XSL/Transform");
698
699 let result = ctx.resolve_namespace("xslt");
700 assert!(result.is_some());
701 assert_eq!(result.unwrap(), "http://www.w3.org/1999/XSL/Transform");
702 }
703
704 #[test]
705 fn test_resolve_unknown_namespace() {
706 let ctx = XPathContext::new(std::ptr::null_mut());
707 assert!(ctx.resolve_namespace("unknown").is_none());
709 }
710
711 #[test]
712 fn test_register_default_namespace() {
713 let mut ctx = XPathContext::new(std::ptr::null_mut());
714 ctx.register_namespace("", "http://example.com/default");
715
716 let result = ctx.resolve_namespace("");
717 assert!(result.is_some());
718 assert_eq!(result.unwrap(), "http://example.com/default");
719 }
720
721 #[test]
722 fn test_namespace_overwrite() {
723 let mut ctx = XPathContext::new(std::ptr::null_mut());
724 ctx.register_namespace("a", "http://example.com/1");
725 ctx.register_namespace("a", "http://example.com/2");
726
727 let result = ctx.resolve_namespace("a");
728 assert_eq!(result.unwrap(), "http://example.com/2");
729 }
730
731 #[test]
734 fn test_register_and_lookup_function() {
735 fn test_func(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
736 Ok(XPathValue::String("test".to_string()))
737 }
738
739 let mut ctx = XPathContext::new(std::ptr::null_mut());
740 ctx.register_function("test:func", test_func);
741
742 let result = ctx.lookup_function("test:func");
743 assert!(result.is_some());
744 }
745
746 #[test]
747 fn test_lookup_unknown_function() {
748 let ctx = XPathContext::new(std::ptr::null_mut());
749 assert!(ctx.lookup_function("nonexistent").is_none());
750 }
751
752 #[test]
753 fn test_function_overwrite() {
754 fn func_a(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
755 Ok(XPathValue::String("a".to_string()))
756 }
757 fn func_b(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
758 Ok(XPathValue::String("b".to_string()))
759 }
760
761 let mut ctx = XPathContext::new(std::ptr::null_mut());
762 ctx.register_function("f", func_a);
763 ctx.register_function("f", func_b);
764
765 let result = ctx.lookup_function("f");
766 assert!(result.is_some());
767
768 if let Some(f) = result {
770 let mut tmp_ctx = XPathContext::new(std::ptr::null_mut());
771 let value = f(&mut tmp_ctx, &[]).unwrap();
772 assert_eq!(value.as_string(), "b");
773 }
774 }
775
776 #[test]
779 fn test_set_and_get_error() {
780 let mut ctx = XPathContext::new(std::ptr::null_mut());
781 assert!(ctx.error.is_none());
782
783 ctx.set_error("something went wrong");
784 assert_eq!(ctx.error.as_deref(), Some("something went wrong"));
785 }
786
787 #[test]
788 fn test_clear_error() {
789 let mut ctx = XPathContext::new(std::ptr::null_mut());
790 ctx.set_error("an error");
791 assert!(ctx.error.is_some());
792
793 ctx.clear_error();
794 assert!(ctx.error.is_none());
795 }
796
797 #[test]
798 fn test_error_overwrite() {
799 let mut ctx = XPathContext::new(std::ptr::null_mut());
800 ctx.set_error("first error");
801 ctx.set_error("second error");
802 assert_eq!(ctx.error.as_deref(), Some("second error"));
803 }
804
805 #[test]
808 fn test_push_pop_recursion() {
809 let mut ctx = XPathContext::new(std::ptr::null_mut());
810 assert_eq!(ctx.recursion_depth, 0);
811
812 assert!(ctx.push_recursion().is_ok());
813 assert_eq!(ctx.recursion_depth, 1);
814
815 ctx.pop_recursion();
816 assert_eq!(ctx.recursion_depth, 0);
817 }
818
819 #[test]
820 fn test_recursion_depth_limit() {
821 let mut ctx = XPathContext::new(std::ptr::null_mut());
822
823 for _ in 0..1000 {
825 assert!(ctx.push_recursion().is_ok());
826 }
827 assert_eq!(ctx.recursion_depth, 1000);
828
829 let result = ctx.push_recursion();
831 assert!(result.is_err());
832 assert!(result.unwrap_err().contains("recursion depth exceeded"));
833
834 for _ in 0..1000 {
836 ctx.pop_recursion();
837 }
838 assert_eq!(ctx.recursion_depth, 0);
839 }
840
841 #[test]
842 #[should_panic(expected = "unbalanced pop_recursion")]
843 fn test_pop_recursion_underflow() {
844 let mut ctx = XPathContext::new(std::ptr::null_mut());
845 ctx.pop_recursion(); }
847
848 #[test]
849 fn test_recursion_nesting() {
850 let mut ctx = XPathContext::new(std::ptr::null_mut());
851
852 assert!(ctx.push_recursion().is_ok());
854 assert!(ctx.push_recursion().is_ok());
855 assert!(ctx.push_recursion().is_ok());
856 assert_eq!(ctx.recursion_depth, 3);
857
858 ctx.pop_recursion();
859 assert_eq!(ctx.recursion_depth, 2);
860
861 ctx.pop_recursion();
862 assert_eq!(ctx.recursion_depth, 1);
863
864 ctx.pop_recursion();
865 assert_eq!(ctx.recursion_depth, 0);
866 }
867
868 #[test]
871 fn test_position_and_last() {
872 let mut ctx = XPathContext::new(std::ptr::null_mut());
873 assert_eq!(ctx.position(), 1);
874 assert_eq!(ctx.last(), 1);
875 }
876
877 #[test]
878 fn test_advance_position() {
879 let mut ctx = XPathContext::new(std::ptr::null_mut());
880 ctx.advance_position();
881 assert_eq!(ctx.position(), 2);
882 assert_eq!(ctx.proximity_position, 2);
883 assert_eq!(ctx.context_position, 2);
884 }
885
886 #[test]
887 fn test_reset_position() {
888 let mut ctx = XPathContext::new(std::ptr::null_mut());
889 ctx.advance_position();
890 ctx.advance_position();
891 ctx.advance_position();
892 assert_eq!(ctx.position(), 4);
893
894 ctx.reset_position();
895 assert_eq!(ctx.position(), 1);
896 assert_eq!(ctx.context_position, 1);
897 }
898
899 #[test]
900 fn test_position_with_context_list() {
901 unsafe {
902 let node1 = create_test_node();
903 let node2 = create_test_node();
904 let node3 = create_test_node();
905 let nodes = vec![node1, node2, node3];
906
907 let mut ctx = XPathContext::new(std::ptr::null_mut());
908 ctx.set_context_list(nodes);
909
910 assert_eq!(ctx.last(), 3);
911 assert_eq!(ctx.position(), 1);
912
913 ctx.advance_position();
914 assert_eq!(ctx.position(), 2);
915
916 ctx.advance_position();
917 assert_eq!(ctx.position(), 3);
918
919 free_test_node(node1);
920 free_test_node(node2);
921 free_test_node(node3);
922 }
923 }
924
925 #[test]
928 fn test_has_context_node() {
929 let mut ctx = XPathContext::new(std::ptr::null_mut());
930 assert!(!ctx.has_context_node());
931
932 unsafe {
933 let node = create_test_node();
934 ctx.set_context_node(node);
935 assert!(ctx.has_context_node());
936 free_test_node(node);
937 }
938 }
939
940 #[test]
943 fn test_reset() {
944 let mut ctx = XPathContext::new(std::ptr::null_mut());
945
946 ctx.set_error("test error");
948 ctx.proximity_position = 5;
949 ctx.context_position = 5;
950 ctx.context_size = 10;
951 ctx.recursion_depth = 3;
952 unsafe {
953 let sentinel = 1 as *mut _xmlNode;
954 ctx.context_list = vec![sentinel];
955 }
956
957 ctx.register_variable("x", XPathValue::Number(42.0));
959 ctx.register_namespace("p", "http://example.com/ns");
960 fn dummy(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
961 Ok(XPathValue::Boolean(true))
962 }
963 ctx.register_function("f", dummy);
964
965 ctx.reset();
966
967 assert!(ctx.context_node.is_null());
969 assert_eq!(ctx.context_position, 1);
970 assert_eq!(ctx.context_size, 1);
971 assert_eq!(ctx.proximity_position, 1);
972 assert!(ctx.error.is_none());
973 assert!(ctx.context_list.is_empty());
974 assert_eq!(ctx.recursion_depth, 0);
975
976 assert!(ctx.resolve_variable("x").is_some());
978 assert!(ctx.resolve_namespace("p").is_some());
979 assert!(ctx.lookup_function("f").is_some());
980 }
981
982 #[test]
985 fn test_callback_fields_default_to_none() {
986 let ctx = XPathContext::new(std::ptr::null_mut());
987 assert!(ctx.var_lookup_func.is_none());
988 assert!(ctx.var_lookup_data.is_null());
989 assert!(ctx.func_lookup_func.is_none());
990 assert!(ctx.func_lookup_data.is_null());
991 }
992
993 #[test]
994 fn test_set_callback_fields() {
995 let mut ctx = XPathContext::new(std::ptr::null_mut());
996
997 unsafe extern "C" fn dummy_var_lookup(
998 _data: *mut c_void,
999 _ns: *const xmlChar,
1000 _name: *const xmlChar,
1001 ) -> *mut _xmlXPathObject {
1002 std::ptr::null_mut()
1003 }
1004
1005 unsafe extern "C" fn dummy_func_lookup(
1006 _data: *mut c_void,
1007 _ns: *const xmlChar,
1008 _name: *const xmlChar,
1009 ) -> *mut c_void {
1010 std::ptr::null_mut()
1011 }
1012
1013 let data_ptr = &mut 42u32 as *mut u32 as *mut c_void;
1014
1015 ctx.var_lookup_func = Some(dummy_var_lookup);
1016 ctx.var_lookup_data = data_ptr;
1017 ctx.func_lookup_func = Some(dummy_func_lookup);
1018 ctx.func_lookup_data = data_ptr;
1019
1020 assert!(ctx.var_lookup_func.is_some());
1021 assert!(!ctx.var_lookup_data.is_null());
1022 assert!(ctx.func_lookup_func.is_some());
1023 assert!(!ctx.func_lookup_data.is_null());
1024 }
1025
1026 #[test]
1029 fn test_context_clone() {
1030 let mut ctx = XPathContext::new(std::ptr::null_mut());
1031 ctx.register_variable("x", XPathValue::Number(10.0));
1032 ctx.register_namespace("ns", "http://example.com/ns");
1033 ctx.set_error("clone test");
1034
1035 let cloned = ctx.clone();
1036 assert_eq!(cloned.document, ctx.document);
1037 assert_eq!(cloned.context_node, ctx.context_node);
1038 assert_eq!(cloned.context_position, ctx.context_position);
1039 assert_eq!(cloned.context_size, ctx.context_size);
1040 assert_eq!(cloned.error, ctx.error);
1041
1042 let var = cloned.resolve_variable("x");
1044 assert!(var.is_some());
1045 assert!((var.unwrap().as_number() - 10.0).abs() < 1e-10);
1046
1047 let ns = cloned.resolve_namespace("ns");
1048 assert!(ns.is_some());
1049 assert_eq!(ns.unwrap(), "http://example.com/ns");
1050 }
1051
1052 #[test]
1055 fn test_context_debug_format() {
1056 let ctx = XPathContext::new(std::ptr::null_mut());
1057 let debug_str = format!("{:?}", ctx);
1058 assert!(debug_str.contains("context_position"));
1059 assert!(debug_str.contains("context_size"));
1060 assert!(debug_str.contains("recursion_depth"));
1061 }
1062
1063 #[test]
1066 fn test_context_size_zero() {
1067 let mut ctx = XPathContext::new(std::ptr::null_mut());
1068 ctx.set_context_list(vec![]);
1069 assert_eq!(ctx.last(), 0);
1070 assert_eq!(ctx.position(), 1);
1071 }
1072
1073 #[test]
1074 fn test_multiple_advancements() {
1075 let mut ctx = XPathContext::new(std::ptr::null_mut());
1076 for i in 1..=10 {
1077 assert_eq!(ctx.position(), i);
1078 ctx.advance_position();
1079 }
1080 assert_eq!(ctx.position(), 11);
1081 }
1082
1083 #[test]
1084 fn test_register_multiple_variables() {
1085 let mut ctx = XPathContext::new(std::ptr::null_mut());
1086 ctx.register_variable("a", XPathValue::Number(1.0));
1087 ctx.register_variable("b", XPathValue::String("two".to_string()));
1088 ctx.register_variable("c", XPathValue::Boolean(true));
1089
1090 assert_eq!(ctx.variables.len(), 3);
1091 assert_eq!(ctx.resolve_variable("a").unwrap().as_number(), 1.0);
1092 assert_eq!(ctx.resolve_variable("b").unwrap().as_string(), "two");
1093 assert!(ctx.resolve_variable("c").unwrap().as_boolean());
1094 }
1095
1096 #[test]
1097 fn test_register_multiple_namespaces() {
1098 let mut ctx = XPathContext::new(std::ptr::null_mut());
1099 ctx.register_namespace("a", "http://example.com/a");
1100 ctx.register_namespace("b", "http://example.com/b");
1101 ctx.register_namespace("c", "http://example.com/c");
1102
1103 assert_eq!(ctx.namespaces.len(), 3);
1104 assert_eq!(ctx.resolve_namespace("a").unwrap(), "http://example.com/a");
1105 assert_eq!(ctx.resolve_namespace("b").unwrap(), "http://example.com/b");
1106 assert_eq!(ctx.resolve_namespace("c").unwrap(), "http://example.com/c");
1107 }
1108
1109 #[test]
1110 fn test_register_multiple_functions() {
1111 fn f1(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1112 Ok(XPathValue::Number(1.0))
1113 }
1114 fn f2(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1115 Ok(XPathValue::Number(2.0))
1116 }
1117 fn f3(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1118 Ok(XPathValue::Number(3.0))
1119 }
1120
1121 let mut ctx = XPathContext::new(std::ptr::null_mut());
1122 ctx.register_function("f1", f1);
1123 ctx.register_function("f2", f2);
1124 ctx.register_function("f3", f3);
1125
1126 assert_eq!(ctx.functions.len(), 3);
1127 }
1128}