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 BoxedXPathFunction =
40 Box<dyn Fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String> + Send + Sync>;
41
42pub type VarLookupFunc =
55 unsafe extern "C" fn(*mut c_void, *const xmlChar, *const xmlChar) -> *mut _xmlXPathObject;
56
57pub type FuncLookupFunc =
70 unsafe extern "C" fn(*mut c_void, *const xmlChar, *const xmlChar) -> *mut c_void;
71
72pub struct XPathContext {
94 pub document: *mut _xmlDoc,
96
97 pub context_node: *mut _xmlNode,
99
100 pub context_position: i32,
102
103 pub context_size: i32,
105
106 pub variables: HashMap<String, XPathValue>,
108
109 pub namespaces: HashMap<String, String>,
111
112 pub functions: HashMap<String, BoxedXPathFunction>,
114
115 pub error: Option<String>,
117
118 pub proximity_position: i32,
120
121 pub context_list: Vec<*mut _xmlNode>,
123
124 pub recursion_depth: u32,
126
127 pub var_lookup_func: Option<VarLookupFunc>,
129
130 pub var_lookup_data: *mut c_void,
132
133 pub func_lookup_func: Option<FuncLookupFunc>,
135
136 pub func_lookup_data: *mut c_void,
138}
139
140impl std::fmt::Debug for XPathContext {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 let names: Vec<&String> = self.functions.keys().collect();
145 f.debug_struct("XPathContext")
146 .field("document", &self.document)
147 .field("context_node", &self.context_node)
148 .field("context_position", &self.context_position)
149 .field("context_size", &self.context_size)
150 .field("variables", &self.variables)
151 .field("namespaces", &self.namespaces)
152 .field("functions", &names)
153 .field("error", &self.error)
154 .field("recursion_depth", &self.recursion_depth)
155 .finish()
156 }
157}
158
159impl Clone for XPathContext {
160 fn clone(&self) -> Self {
161 let mut cloned = XPathContext::new(self.document);
164 cloned.context_node = self.context_node;
165 cloned.context_position = self.context_position;
166 cloned.context_size = self.context_size;
167 cloned.variables = self.variables.clone();
168 cloned.namespaces = self.namespaces.clone();
169 cloned.error = self.error.clone();
170 cloned.proximity_position = self.proximity_position;
171 cloned.context_list = self.context_list.clone();
172 cloned.recursion_depth = self.recursion_depth;
173 cloned.var_lookup_func = self.var_lookup_func;
174 cloned.var_lookup_data = self.var_lookup_data;
175 cloned.func_lookup_func = self.func_lookup_func;
176 cloned.func_lookup_data = self.func_lookup_data;
177 cloned
178 }
179}
180
181impl XPathContext {
182 pub fn new(doc: *mut _xmlDoc) -> Self {
196 Self {
197 document: doc,
198 context_node: std::ptr::null_mut(),
199 context_position: 1,
200 context_size: 1,
201 variables: HashMap::new(),
202 namespaces: HashMap::new(),
203 functions: HashMap::new(),
204 error: None,
205 proximity_position: 1,
206 context_list: Vec::new(),
207 recursion_depth: 0,
208 var_lookup_func: None,
209 var_lookup_data: std::ptr::null_mut(),
210 func_lookup_func: None,
211 func_lookup_data: std::ptr::null_mut(),
212 }
213 }
214
215 pub fn set_context_node(&mut self, node: *mut _xmlNode) {
224 self.context_node = node;
225 if node.is_null() {
226 self.context_list.clear();
227 self.context_position = 1;
228 self.context_size = 1;
229 self.proximity_position = 1;
230 } else {
231 self.context_list = vec![node];
232 self.context_position = 1;
233 self.context_size = 1;
234 self.proximity_position = 1;
235 }
236 }
237
238 pub fn set_context_list(&mut self, nodes: Vec<*mut _xmlNode>) {
246 self.context_size = nodes.len() as i32;
247 self.context_list = nodes;
248 self.context_position = 1;
249 self.proximity_position = 1;
250 }
251
252 pub fn resolve_variable(&self, name: &str) -> Option<XPathValue> {
268 if let Some(value) = self.variables.get(name) {
270 return Some(value.clone());
271 }
272
273 if let Some(lookup) = self.var_lookup_func {
275 let c_name: Vec<xmlChar> = name.bytes().collect();
277 let result = unsafe { lookup(self.var_lookup_data, std::ptr::null(), c_name.as_ptr()) };
280 if !result.is_null() {
281 unsafe {
286 let _ = result; }
291 }
292 }
293
294 None
295 }
296
297 pub fn resolve_namespace(&self, prefix: &str) -> Option<String> {
305 if let Some(uri) = self.namespaces.get(prefix) {
307 return Some(uri.clone());
308 }
309
310 let mut current = self.context_node;
313 while !current.is_null() {
314 unsafe {
317 let mut ns = (*current).nsDef;
318 while !ns.is_null() {
319 let ns_prefix = (*ns).prefix;
320 let ns_href = (*ns).href;
321
322 let prefix_matches = if ns_prefix.is_null() {
324 prefix.is_empty()
327 } else {
328 let mut len = 0;
330 while *ns_prefix.add(len) != 0 {
331 len += 1;
332 }
333 let slice = std::slice::from_raw_parts(ns_prefix, len);
334 slice == prefix.as_bytes()
335 };
336
337 if prefix_matches {
338 let mut len = 0;
340 while *ns_href.add(len) != 0 {
341 len += 1;
342 }
343 let slice = std::slice::from_raw_parts(ns_href, len);
344 return Some(String::from_utf8_lossy(slice).into_owned());
345 }
346
347 ns = (*ns).next;
348 }
349 }
350
351 unsafe {
354 current = (*current).parent;
355 }
356 }
357
358 None
359 }
360
361 pub fn lookup_function(&self, name: &str) -> Option<&BoxedXPathFunction> {
368 if let Some(func) = self.functions.get(name) {
370 return Some(func);
371 }
372
373 if let Some(lookup) = self.func_lookup_func {
375 let c_name: Vec<xmlChar> = name.bytes().collect();
376 let _result =
378 unsafe { lookup(self.func_lookup_data, std::ptr::null(), c_name.as_ptr()) };
379 }
383
384 None
385 }
386
387 pub fn register_function<F>(&mut self, name: &str, func: F)
394 where
395 F: Fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String>
396 + Send
397 + Sync
398 + 'static,
399 {
400 self.functions.insert(name.to_string(), Box::new(func));
401 }
402
403 pub fn register_variable(&mut self, name: &str, value: XPathValue) {
409 self.variables.insert(name.to_string(), value);
410 }
411
412 pub fn unregister_variable(&mut self, name: &str) {
417 self.variables.remove(name);
418 }
419
420 pub fn register_namespace(&mut self, prefix: &str, uri: &str) {
425 self.namespaces.insert(prefix.to_string(), uri.to_string());
426 }
427
428 pub fn set_error(&mut self, msg: &str) {
432 self.error = Some(msg.to_string());
433 }
434
435 pub fn clear_error(&mut self) {
437 self.error = None;
438 }
439
440 pub fn push_recursion(&mut self) -> Result<(), String> {
448 const MAX_RECURSION_DEPTH: u32 = 1000;
449 if self.recursion_depth >= MAX_RECURSION_DEPTH {
450 return Err(
451 "XPath evaluation recursion depth exceeded (infinite recursion?)".to_string(),
452 );
453 }
454 self.recursion_depth += 1;
455 Ok(())
456 }
457
458 pub fn pop_recursion(&mut self) {
468 assert!(
469 self.recursion_depth > 0,
470 "unbalanced pop_recursion: recursion_depth is already 0"
471 );
472 self.recursion_depth -= 1;
473 }
474
475 pub fn has_context_node(&self) -> bool {
477 !self.context_node.is_null()
478 }
479
480 pub fn reset(&mut self) {
485 self.context_node = std::ptr::null_mut();
486 self.context_position = 1;
487 self.context_size = 1;
488 self.error = None;
489 self.proximity_position = 1;
490 self.context_list.clear();
491 self.recursion_depth = 0;
492 }
493
494 pub fn position(&self) -> i32 {
498 self.proximity_position
499 }
500
501 pub fn last(&self) -> i32 {
505 self.context_size
506 }
507
508 pub fn advance_position(&mut self) {
513 self.proximity_position += 1;
514 self.context_position = self.proximity_position;
515 }
516
517 pub fn reset_position(&mut self) {
519 self.proximity_position = 1;
520 self.context_position = 1;
521 }
522}
523
524impl Default for XPathContext {
525 fn default() -> Self {
530 Self::new(std::ptr::null_mut())
531 }
532}
533
534#[cfg(test)]
539mod tests {
540 use super::*;
541 use crate::xml::xpath::ast::Expr;
542 use crate::xml::xpath::types::NodeSet;
543
544 unsafe fn create_test_doc() -> *mut _xmlDoc {
550 let layout = std::alloc::Layout::new::<_xmlDoc>();
552 let ptr = std::alloc::alloc_zeroed(layout) as *mut _xmlDoc;
553 assert!(!ptr.is_null(), "failed to allocate test document");
554 ptr
555 }
556
557 unsafe fn create_test_node() -> *mut _xmlNode {
561 let layout = std::alloc::Layout::new::<_xmlNode>();
562 let ptr = std::alloc::alloc_zeroed(layout) as *mut _xmlNode;
563 assert!(!ptr.is_null(), "failed to allocate test node");
564 ptr
565 }
566
567 unsafe fn free_test_doc(doc: *mut _xmlDoc) {
569 if !doc.is_null() {
570 let layout = std::alloc::Layout::new::<_xmlDoc>();
571 std::alloc::dealloc(doc as *mut u8, layout);
572 }
573 }
574
575 unsafe fn free_test_node(node: *mut _xmlNode) {
577 if !node.is_null() {
578 let layout = std::alloc::Layout::new::<_xmlNode>();
579 std::alloc::dealloc(node as *mut u8, layout);
580 }
581 }
582
583 #[test]
586 fn test_new_context() {
587 let ctx = XPathContext::new(std::ptr::null_mut());
588 assert!(ctx.document.is_null());
589 assert!(ctx.context_node.is_null());
590 assert_eq!(ctx.context_position, 1);
591 assert_eq!(ctx.context_size, 1);
592 assert!(ctx.variables.is_empty());
593 assert!(ctx.namespaces.is_empty());
594 assert!(ctx.functions.is_empty());
595 assert!(ctx.error.is_none());
596 assert_eq!(ctx.proximity_position, 1);
597 assert!(ctx.context_list.is_empty());
598 assert_eq!(ctx.recursion_depth, 0);
599 assert!(ctx.var_lookup_func.is_none());
600 assert!(ctx.var_lookup_data.is_null());
601 assert!(ctx.func_lookup_func.is_none());
602 assert!(ctx.func_lookup_data.is_null());
603 }
604
605 #[test]
606 fn test_default_context() {
607 let ctx = XPathContext::default();
608 assert!(ctx.document.is_null());
609 assert_eq!(ctx.context_position, 1);
610 }
611
612 #[test]
613 fn test_new_with_doc() {
614 unsafe {
615 let doc = create_test_doc();
616 let ctx = XPathContext::new(doc);
617 assert_eq!(ctx.document, doc);
618 free_test_doc(doc);
619 }
620 }
621
622 #[test]
625 fn test_set_context_node_non_null() {
626 unsafe {
627 let node = create_test_node();
628 let mut ctx = XPathContext::new(std::ptr::null_mut());
629 ctx.set_context_node(node);
630
631 assert_eq!(ctx.context_node, node);
632 assert_eq!(ctx.context_position, 1);
633 assert_eq!(ctx.context_size, 1);
634 assert_eq!(ctx.proximity_position, 1);
635 assert_eq!(ctx.context_list.len(), 1);
636 assert_eq!(ctx.context_list[0], node);
637
638 free_test_node(node);
639 }
640 }
641
642 #[test]
643 fn test_set_context_node_null() {
644 let mut ctx = XPathContext::new(std::ptr::null_mut());
645 let sentinel = 1 as *mut _xmlNode;
648 ctx.context_list = vec![sentinel];
649 ctx.context_position = 5;
650 ctx.context_size = 5;
651 ctx.proximity_position = 5;
652
653 ctx.set_context_node(std::ptr::null_mut());
655 assert!(ctx.context_node.is_null());
656 assert!(ctx.context_list.is_empty());
657 assert_eq!(ctx.context_position, 1);
658 assert_eq!(ctx.context_size, 1);
659 assert_eq!(ctx.proximity_position, 1);
660 }
661
662 #[test]
665 fn test_set_context_list() {
666 unsafe {
667 let node1 = create_test_node();
668 let node2 = create_test_node();
669 let nodes = vec![node1, node2];
670
671 let mut ctx = XPathContext::new(std::ptr::null_mut());
672 ctx.set_context_list(nodes.clone());
673
674 assert_eq!(ctx.context_list.len(), 2);
675 assert_eq!(ctx.context_size, 2);
676 assert_eq!(ctx.context_position, 1);
677 assert_eq!(ctx.proximity_position, 1);
678
679 free_test_node(node1);
680 free_test_node(node2);
681 }
682 }
683
684 #[test]
685 fn test_set_context_list_empty() {
686 let mut ctx = XPathContext::new(std::ptr::null_mut());
687 ctx.set_context_list(vec![]);
688
689 assert!(ctx.context_list.is_empty());
690 assert_eq!(ctx.context_size, 0);
691 assert_eq!(ctx.context_position, 1);
692 }
693
694 #[test]
697 fn test_register_and_resolve_variable() {
698 let mut ctx = XPathContext::new(std::ptr::null_mut());
699 ctx.register_variable("foo", XPathValue::String("bar".to_string()));
700
701 let result = ctx.resolve_variable("foo");
702 assert!(result.is_some());
703 assert_eq!(result.unwrap().as_string(), "bar");
704 }
705
706 #[test]
707 fn test_resolve_unknown_variable() {
708 let ctx = XPathContext::new(std::ptr::null_mut());
709 assert!(ctx.resolve_variable("nonexistent").is_none());
710 }
711
712 #[test]
713 fn test_register_variable_number() {
714 let mut ctx = XPathContext::new(std::ptr::null_mut());
715 ctx.register_variable("pi", XPathValue::Number(3.14159));
716
717 let result = ctx.resolve_variable("pi");
718 assert!(result.is_some());
719 let val = result.unwrap();
720 assert!((val.as_number() - 3.14159).abs() < 1e-10);
721 }
722
723 #[test]
724 fn test_register_variable_boolean() {
725 let mut ctx = XPathContext::new(std::ptr::null_mut());
726 ctx.register_variable("flag", XPathValue::Boolean(true));
727
728 let result = ctx.resolve_variable("flag");
729 assert!(result.is_some());
730 assert!(result.unwrap().as_boolean());
731 }
732
733 #[test]
734 fn test_register_variable_nodeset() {
735 let mut ctx = XPathContext::new(std::ptr::null_mut());
736 let ns = NodeSet::new();
737 ctx.register_variable("nodes", XPathValue::NodeSet(ns));
738
739 let result = ctx.resolve_variable("nodes");
740 assert!(result.is_some());
741 assert!(matches!(result.unwrap(), XPathValue::NodeSet(_)));
742 }
743
744 #[test]
745 fn test_variable_overwrite() {
746 let mut ctx = XPathContext::new(std::ptr::null_mut());
747 ctx.register_variable("x", XPathValue::Number(1.0));
748 ctx.register_variable("x", XPathValue::Number(2.0));
749
750 let result = ctx.resolve_variable("x");
751 assert!(result.is_some());
752 assert!((result.unwrap().as_number() - 2.0).abs() < 1e-10);
753 }
754
755 #[test]
758 fn test_register_and_resolve_namespace() {
759 let mut ctx = XPathContext::new(std::ptr::null_mut());
760 ctx.register_namespace("xslt", "http://www.w3.org/1999/XSL/Transform");
761
762 let result = ctx.resolve_namespace("xslt");
763 assert!(result.is_some());
764 assert_eq!(result.unwrap(), "http://www.w3.org/1999/XSL/Transform");
765 }
766
767 #[test]
768 fn test_resolve_unknown_namespace() {
769 let ctx = XPathContext::new(std::ptr::null_mut());
770 assert!(ctx.resolve_namespace("unknown").is_none());
772 }
773
774 #[test]
775 fn test_register_default_namespace() {
776 let mut ctx = XPathContext::new(std::ptr::null_mut());
777 ctx.register_namespace("", "http://example.com/default");
778
779 let result = ctx.resolve_namespace("");
780 assert!(result.is_some());
781 assert_eq!(result.unwrap(), "http://example.com/default");
782 }
783
784 #[test]
785 fn test_namespace_overwrite() {
786 let mut ctx = XPathContext::new(std::ptr::null_mut());
787 ctx.register_namespace("a", "http://example.com/1");
788 ctx.register_namespace("a", "http://example.com/2");
789
790 let result = ctx.resolve_namespace("a");
791 assert_eq!(result.unwrap(), "http://example.com/2");
792 }
793
794 #[test]
797 fn test_register_and_lookup_function() {
798 fn test_func(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
799 Ok(XPathValue::String("test".to_string()))
800 }
801
802 let mut ctx = XPathContext::new(std::ptr::null_mut());
803 ctx.register_function("test:func", test_func);
804
805 let result = ctx.lookup_function("test:func");
806 assert!(result.is_some());
807 }
808
809 #[test]
810 fn test_lookup_unknown_function() {
811 let ctx = XPathContext::new(std::ptr::null_mut());
812 assert!(ctx.lookup_function("nonexistent").is_none());
813 }
814
815 #[test]
816 fn test_function_overwrite() {
817 fn func_a(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
818 Ok(XPathValue::String("a".to_string()))
819 }
820 fn func_b(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
821 Ok(XPathValue::String("b".to_string()))
822 }
823
824 let mut ctx = XPathContext::new(std::ptr::null_mut());
825 ctx.register_function("f", func_a);
826 ctx.register_function("f", func_b);
827
828 let result = ctx.lookup_function("f");
829 assert!(result.is_some());
830
831 if let Some(f) = result {
833 let mut tmp_ctx = XPathContext::new(std::ptr::null_mut());
834 let value = f(&mut tmp_ctx, &[]).unwrap();
835 assert_eq!(value.as_string(), "b");
836 }
837 }
838
839 #[test]
842 fn test_set_and_get_error() {
843 let mut ctx = XPathContext::new(std::ptr::null_mut());
844 assert!(ctx.error.is_none());
845
846 ctx.set_error("something went wrong");
847 assert_eq!(ctx.error.as_deref(), Some("something went wrong"));
848 }
849
850 #[test]
851 fn test_clear_error() {
852 let mut ctx = XPathContext::new(std::ptr::null_mut());
853 ctx.set_error("an error");
854 assert!(ctx.error.is_some());
855
856 ctx.clear_error();
857 assert!(ctx.error.is_none());
858 }
859
860 #[test]
861 fn test_error_overwrite() {
862 let mut ctx = XPathContext::new(std::ptr::null_mut());
863 ctx.set_error("first error");
864 ctx.set_error("second error");
865 assert_eq!(ctx.error.as_deref(), Some("second error"));
866 }
867
868 #[test]
871 fn test_push_pop_recursion() {
872 let mut ctx = XPathContext::new(std::ptr::null_mut());
873 assert_eq!(ctx.recursion_depth, 0);
874
875 assert!(ctx.push_recursion().is_ok());
876 assert_eq!(ctx.recursion_depth, 1);
877
878 ctx.pop_recursion();
879 assert_eq!(ctx.recursion_depth, 0);
880 }
881
882 #[test]
883 fn test_recursion_depth_limit() {
884 let mut ctx = XPathContext::new(std::ptr::null_mut());
885
886 for _ in 0..1000 {
888 assert!(ctx.push_recursion().is_ok());
889 }
890 assert_eq!(ctx.recursion_depth, 1000);
891
892 let result = ctx.push_recursion();
894 assert!(result.is_err());
895 assert!(result.unwrap_err().contains("recursion depth exceeded"));
896
897 for _ in 0..1000 {
899 ctx.pop_recursion();
900 }
901 assert_eq!(ctx.recursion_depth, 0);
902 }
903
904 #[test]
905 #[should_panic(expected = "unbalanced pop_recursion")]
906 fn test_pop_recursion_underflow() {
907 let mut ctx = XPathContext::new(std::ptr::null_mut());
908 ctx.pop_recursion(); }
910
911 #[test]
912 fn test_recursion_nesting() {
913 let mut ctx = XPathContext::new(std::ptr::null_mut());
914
915 assert!(ctx.push_recursion().is_ok());
917 assert!(ctx.push_recursion().is_ok());
918 assert!(ctx.push_recursion().is_ok());
919 assert_eq!(ctx.recursion_depth, 3);
920
921 ctx.pop_recursion();
922 assert_eq!(ctx.recursion_depth, 2);
923
924 ctx.pop_recursion();
925 assert_eq!(ctx.recursion_depth, 1);
926
927 ctx.pop_recursion();
928 assert_eq!(ctx.recursion_depth, 0);
929 }
930
931 #[test]
934 fn test_position_and_last() {
935 let mut ctx = XPathContext::new(std::ptr::null_mut());
936 assert_eq!(ctx.position(), 1);
937 assert_eq!(ctx.last(), 1);
938 }
939
940 #[test]
941 fn test_advance_position() {
942 let mut ctx = XPathContext::new(std::ptr::null_mut());
943 ctx.advance_position();
944 assert_eq!(ctx.position(), 2);
945 assert_eq!(ctx.proximity_position, 2);
946 assert_eq!(ctx.context_position, 2);
947 }
948
949 #[test]
950 fn test_reset_position() {
951 let mut ctx = XPathContext::new(std::ptr::null_mut());
952 ctx.advance_position();
953 ctx.advance_position();
954 ctx.advance_position();
955 assert_eq!(ctx.position(), 4);
956
957 ctx.reset_position();
958 assert_eq!(ctx.position(), 1);
959 assert_eq!(ctx.context_position, 1);
960 }
961
962 #[test]
963 fn test_position_with_context_list() {
964 unsafe {
965 let node1 = create_test_node();
966 let node2 = create_test_node();
967 let node3 = create_test_node();
968 let nodes = vec![node1, node2, node3];
969
970 let mut ctx = XPathContext::new(std::ptr::null_mut());
971 ctx.set_context_list(nodes);
972
973 assert_eq!(ctx.last(), 3);
974 assert_eq!(ctx.position(), 1);
975
976 ctx.advance_position();
977 assert_eq!(ctx.position(), 2);
978
979 ctx.advance_position();
980 assert_eq!(ctx.position(), 3);
981
982 free_test_node(node1);
983 free_test_node(node2);
984 free_test_node(node3);
985 }
986 }
987
988 #[test]
991 fn test_has_context_node() {
992 let mut ctx = XPathContext::new(std::ptr::null_mut());
993 assert!(!ctx.has_context_node());
994
995 unsafe {
996 let node = create_test_node();
997 ctx.set_context_node(node);
998 assert!(ctx.has_context_node());
999 free_test_node(node);
1000 }
1001 }
1002
1003 #[test]
1006 fn test_reset() {
1007 let mut ctx = XPathContext::new(std::ptr::null_mut());
1008
1009 ctx.set_error("test error");
1011 ctx.proximity_position = 5;
1012 ctx.context_position = 5;
1013 ctx.context_size = 10;
1014 ctx.recursion_depth = 3;
1015 unsafe {
1016 let sentinel = 1 as *mut _xmlNode;
1017 ctx.context_list = vec![sentinel];
1018 }
1019
1020 ctx.register_variable("x", XPathValue::Number(42.0));
1022 ctx.register_namespace("p", "http://example.com/ns");
1023 fn dummy(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1024 Ok(XPathValue::Boolean(true))
1025 }
1026 ctx.register_function("f", dummy);
1027
1028 ctx.reset();
1029
1030 assert!(ctx.context_node.is_null());
1032 assert_eq!(ctx.context_position, 1);
1033 assert_eq!(ctx.context_size, 1);
1034 assert_eq!(ctx.proximity_position, 1);
1035 assert!(ctx.error.is_none());
1036 assert!(ctx.context_list.is_empty());
1037 assert_eq!(ctx.recursion_depth, 0);
1038
1039 assert!(ctx.resolve_variable("x").is_some());
1041 assert!(ctx.resolve_namespace("p").is_some());
1042 assert!(ctx.lookup_function("f").is_some());
1043 }
1044
1045 #[test]
1048 fn test_callback_fields_default_to_none() {
1049 let ctx = XPathContext::new(std::ptr::null_mut());
1050 assert!(ctx.var_lookup_func.is_none());
1051 assert!(ctx.var_lookup_data.is_null());
1052 assert!(ctx.func_lookup_func.is_none());
1053 assert!(ctx.func_lookup_data.is_null());
1054 }
1055
1056 #[test]
1057 fn test_set_callback_fields() {
1058 let mut ctx = XPathContext::new(std::ptr::null_mut());
1059
1060 unsafe extern "C" fn dummy_var_lookup(
1061 _data: *mut c_void,
1062 _ns: *const xmlChar,
1063 _name: *const xmlChar,
1064 ) -> *mut _xmlXPathObject {
1065 std::ptr::null_mut()
1066 }
1067
1068 unsafe extern "C" fn dummy_func_lookup(
1069 _data: *mut c_void,
1070 _ns: *const xmlChar,
1071 _name: *const xmlChar,
1072 ) -> *mut c_void {
1073 std::ptr::null_mut()
1074 }
1075
1076 let data_ptr = &mut 42u32 as *mut u32 as *mut c_void;
1077
1078 ctx.var_lookup_func = Some(dummy_var_lookup);
1079 ctx.var_lookup_data = data_ptr;
1080 ctx.func_lookup_func = Some(dummy_func_lookup);
1081 ctx.func_lookup_data = data_ptr;
1082
1083 assert!(ctx.var_lookup_func.is_some());
1084 assert!(!ctx.var_lookup_data.is_null());
1085 assert!(ctx.func_lookup_func.is_some());
1086 assert!(!ctx.func_lookup_data.is_null());
1087 }
1088
1089 #[test]
1092 fn test_context_clone() {
1093 let mut ctx = XPathContext::new(std::ptr::null_mut());
1094 ctx.register_variable("x", XPathValue::Number(10.0));
1095 ctx.register_namespace("ns", "http://example.com/ns");
1096 ctx.set_error("clone test");
1097
1098 let cloned = ctx.clone();
1099 assert_eq!(cloned.document, ctx.document);
1100 assert_eq!(cloned.context_node, ctx.context_node);
1101 assert_eq!(cloned.context_position, ctx.context_position);
1102 assert_eq!(cloned.context_size, ctx.context_size);
1103 assert_eq!(cloned.error, ctx.error);
1104
1105 let var = cloned.resolve_variable("x");
1107 assert!(var.is_some());
1108 assert!((var.unwrap().as_number() - 10.0).abs() < 1e-10);
1109
1110 let ns = cloned.resolve_namespace("ns");
1111 assert!(ns.is_some());
1112 assert_eq!(ns.unwrap(), "http://example.com/ns");
1113 }
1114
1115 #[test]
1118 fn test_context_debug_format() {
1119 let ctx = XPathContext::new(std::ptr::null_mut());
1120 let debug_str = format!("{:?}", ctx);
1121 assert!(debug_str.contains("context_position"));
1122 assert!(debug_str.contains("context_size"));
1123 assert!(debug_str.contains("recursion_depth"));
1124 }
1125
1126 #[test]
1129 fn test_context_size_zero() {
1130 let mut ctx = XPathContext::new(std::ptr::null_mut());
1131 ctx.set_context_list(vec![]);
1132 assert_eq!(ctx.last(), 0);
1133 assert_eq!(ctx.position(), 1);
1134 }
1135
1136 #[test]
1137 fn test_multiple_advancements() {
1138 let mut ctx = XPathContext::new(std::ptr::null_mut());
1139 for i in 1..=10 {
1140 assert_eq!(ctx.position(), i);
1141 ctx.advance_position();
1142 }
1143 assert_eq!(ctx.position(), 11);
1144 }
1145
1146 #[test]
1147 fn test_register_multiple_variables() {
1148 let mut ctx = XPathContext::new(std::ptr::null_mut());
1149 ctx.register_variable("a", XPathValue::Number(1.0));
1150 ctx.register_variable("b", XPathValue::String("two".to_string()));
1151 ctx.register_variable("c", XPathValue::Boolean(true));
1152
1153 assert_eq!(ctx.variables.len(), 3);
1154 assert_eq!(ctx.resolve_variable("a").unwrap().as_number(), 1.0);
1155 assert_eq!(ctx.resolve_variable("b").unwrap().as_string(), "two");
1156 assert!(ctx.resolve_variable("c").unwrap().as_boolean());
1157 }
1158
1159 #[test]
1160 fn test_register_multiple_namespaces() {
1161 let mut ctx = XPathContext::new(std::ptr::null_mut());
1162 ctx.register_namespace("a", "http://example.com/a");
1163 ctx.register_namespace("b", "http://example.com/b");
1164 ctx.register_namespace("c", "http://example.com/c");
1165
1166 assert_eq!(ctx.namespaces.len(), 3);
1167 assert_eq!(ctx.resolve_namespace("a").unwrap(), "http://example.com/a");
1168 assert_eq!(ctx.resolve_namespace("b").unwrap(), "http://example.com/b");
1169 assert_eq!(ctx.resolve_namespace("c").unwrap(), "http://example.com/c");
1170 }
1171
1172 #[test]
1173 fn test_register_multiple_functions() {
1174 fn f1(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1175 Ok(XPathValue::Number(1.0))
1176 }
1177 fn f2(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1178 Ok(XPathValue::Number(2.0))
1179 }
1180 fn f3(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1181 Ok(XPathValue::Number(3.0))
1182 }
1183
1184 let mut ctx = XPathContext::new(std::ptr::null_mut());
1185 ctx.register_function("f1", f1);
1186 ctx.register_function("f2", f2);
1187 ctx.register_function("f3", f3);
1188
1189 assert_eq!(ctx.functions.len(), 3);
1190 }
1191}