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 FunctionLookupFn =
46 Box<dyn Fn(&XPathContext, &str) -> Option<BoxedXPathFunction> + Send + Sync>;
47
48pub type VarLookupFunc =
61 unsafe extern "C" fn(*mut c_void, *const xmlChar, *const xmlChar) -> *mut _xmlXPathObject;
62
63pub type FuncLookupFunc =
76 unsafe extern "C" fn(*mut c_void, *const xmlChar, *const xmlChar) -> *mut c_void;
77
78pub struct XPathContext {
100 pub document: *mut _xmlDoc,
102
103 pub context_node: *mut _xmlNode,
105
106 pub context_position: i32,
108
109 pub context_size: i32,
111
112 pub variables: HashMap<String, XPathValue>,
114
115 pub namespaces: HashMap<String, String>,
117
118 pub functions: HashMap<String, BoxedXPathFunction>,
120
121 pub function_lookup: Option<FunctionLookupFn>,
124
125 pub error: Option<String>,
127
128 pub proximity_position: i32,
130
131 pub context_list: Vec<*mut _xmlNode>,
133
134 pub recursion_depth: u32,
136
137 pub var_lookup_func: Option<VarLookupFunc>,
139
140 pub var_lookup_data: *mut c_void,
142
143 pub func_lookup_func: Option<FuncLookupFunc>,
145
146 pub func_lookup_data: *mut c_void,
148}
149
150impl std::fmt::Debug for XPathContext {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 let names: Vec<&String> = self.functions.keys().collect();
155 f.debug_struct("XPathContext")
156 .field("document", &self.document)
157 .field("context_node", &self.context_node)
158 .field("context_position", &self.context_position)
159 .field("context_size", &self.context_size)
160 .field("variables", &self.variables)
161 .field("namespaces", &self.namespaces)
162 .field("functions", &names)
163 .field("error", &self.error)
164 .field("recursion_depth", &self.recursion_depth)
165 .finish()
166 }
167}
168
169impl Clone for XPathContext {
170 fn clone(&self) -> Self {
171 let mut cloned = XPathContext::new(self.document);
174 cloned.context_node = self.context_node;
175 cloned.context_position = self.context_position;
176 cloned.context_size = self.context_size;
177 cloned.variables = self.variables.clone();
178 cloned.namespaces = self.namespaces.clone();
179 cloned.error = self.error.clone();
180 cloned.proximity_position = self.proximity_position;
181 cloned.context_list = self.context_list.clone();
182 cloned.recursion_depth = self.recursion_depth;
183 cloned.var_lookup_func = self.var_lookup_func;
184 cloned.var_lookup_data = self.var_lookup_data;
185 cloned.func_lookup_func = self.func_lookup_func;
186 cloned.func_lookup_data = self.func_lookup_data;
187 cloned
188 }
189}
190
191impl XPathContext {
192 pub fn new(doc: *mut _xmlDoc) -> Self {
206 Self {
207 document: doc,
208 context_node: std::ptr::null_mut(),
209 context_position: 1,
210 context_size: 1,
211 variables: HashMap::new(),
212 namespaces: HashMap::new(),
213 functions: HashMap::new(),
214 function_lookup: None,
215 error: None,
216 proximity_position: 1,
217 context_list: Vec::new(),
218 recursion_depth: 0,
219 var_lookup_func: None,
220 var_lookup_data: std::ptr::null_mut(),
221 func_lookup_func: None,
222 func_lookup_data: std::ptr::null_mut(),
223 }
224 }
225
226 pub fn set_context_node(&mut self, node: *mut _xmlNode) {
235 self.context_node = node;
236 if node.is_null() {
237 self.context_list.clear();
238 self.context_position = 1;
239 self.context_size = 1;
240 self.proximity_position = 1;
241 } else {
242 self.context_list = vec![node];
243 self.context_position = 1;
244 self.context_size = 1;
245 self.proximity_position = 1;
246 }
247 }
248
249 pub fn set_context_list(&mut self, nodes: Vec<*mut _xmlNode>) {
257 self.context_size = nodes.len() as i32;
258 self.context_list = nodes;
259 self.context_position = 1;
260 self.proximity_position = 1;
261 }
262
263 pub fn resolve_variable(&self, name: &str) -> Option<XPathValue> {
279 if let Some(value) = self.variables.get(name) {
281 return Some(value.clone());
282 }
283
284 if let Some(lookup) = self.var_lookup_func {
286 let c_name: Vec<xmlChar> = name.bytes().collect();
288 let result = unsafe { lookup(self.var_lookup_data, std::ptr::null(), c_name.as_ptr()) };
291 if !result.is_null() {
292 {
297 let _ = result; }
302 }
303 }
304
305 None
306 }
307
308 pub fn resolve_namespace(&self, prefix: &str) -> Option<String> {
316 if let Some(uri) = self.namespaces.get(prefix) {
318 return Some(uri.clone());
319 }
320
321 let mut current = self.context_node;
324 while !current.is_null() {
325 unsafe {
328 let mut ns = (*current).nsDef;
329 while !ns.is_null() {
330 let ns_prefix = (*ns).prefix;
331 let ns_href = (*ns).href;
332
333 let prefix_matches = if ns_prefix.is_null() {
335 prefix.is_empty()
338 } else {
339 let mut len = 0;
341 while *ns_prefix.add(len) != 0 {
342 len += 1;
343 }
344 let slice = std::slice::from_raw_parts(ns_prefix, len);
345 slice == prefix.as_bytes()
346 };
347
348 if prefix_matches {
349 let mut len = 0;
351 while *ns_href.add(len) != 0 {
352 len += 1;
353 }
354 let slice = std::slice::from_raw_parts(ns_href, len);
355 return Some(String::from_utf8_lossy(slice).into_owned());
356 }
357
358 ns = (*ns).next;
359 }
360 }
361
362 unsafe {
365 current = (*current).parent;
366 }
367 }
368
369 None
370 }
371
372 pub fn lookup_function(&mut self, name: &str) -> Option<&BoxedXPathFunction> {
379 if self.functions.contains_key(name) {
381 return self.functions.get(name);
382 }
383
384 if let Some(lookup) = &self.function_lookup {
387 if let Some(func) = lookup(self, name) {
388 self.functions.insert(name.to_string(), func);
389 return self.functions.get(name);
390 }
391 }
392
393 if let Some(lookup) = self.func_lookup_func {
395 let c_name: Vec<xmlChar> = name.bytes().collect();
396 let _result =
398 unsafe { lookup(self.func_lookup_data, std::ptr::null(), c_name.as_ptr()) };
399 }
403
404 None
405 }
406
407 pub fn register_function<F>(&mut self, name: &str, func: F)
414 where
415 F: Fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String>
416 + Send
417 + Sync
418 + 'static,
419 {
420 self.functions.insert(name.to_string(), Box::new(func));
421 }
422
423 pub fn register_variable(&mut self, name: &str, value: XPathValue) {
429 self.variables.insert(name.to_string(), value);
430 }
431
432 pub fn unregister_variable(&mut self, name: &str) {
437 self.variables.remove(name);
438 }
439
440 pub fn register_namespace(&mut self, prefix: &str, uri: &str) {
445 self.namespaces.insert(prefix.to_string(), uri.to_string());
446 }
447
448 pub fn set_error(&mut self, msg: &str) {
452 self.error = Some(msg.to_string());
453 }
454
455 pub fn clear_error(&mut self) {
457 self.error = None;
458 }
459
460 pub fn push_recursion(&mut self) -> Result<(), String> {
468 const MAX_RECURSION_DEPTH: u32 = 1000;
469 if self.recursion_depth >= MAX_RECURSION_DEPTH {
470 return Err(
471 "XPath evaluation recursion depth exceeded (infinite recursion?)".to_string(),
472 );
473 }
474 self.recursion_depth += 1;
475 Ok(())
476 }
477
478 pub fn pop_recursion(&mut self) {
488 assert!(
489 self.recursion_depth > 0,
490 "unbalanced pop_recursion: recursion_depth is already 0"
491 );
492 self.recursion_depth -= 1;
493 }
494
495 pub const fn has_context_node(&self) -> bool {
497 !self.context_node.is_null()
498 }
499
500 pub fn reset(&mut self) {
505 self.context_node = std::ptr::null_mut();
506 self.context_position = 1;
507 self.context_size = 1;
508 self.error = None;
509 self.proximity_position = 1;
510 self.context_list.clear();
511 self.recursion_depth = 0;
512 }
513
514 pub const fn position(&self) -> i32 {
518 self.proximity_position
519 }
520
521 pub const fn last(&self) -> i32 {
525 self.context_size
526 }
527
528 pub const fn advance_position(&mut self) {
533 self.proximity_position += 1;
534 self.context_position = self.proximity_position;
535 }
536
537 pub const fn reset_position(&mut self) {
539 self.proximity_position = 1;
540 self.context_position = 1;
541 }
542}
543
544impl Default for XPathContext {
545 fn default() -> Self {
550 Self::new(std::ptr::null_mut())
551 }
552}
553
554#[cfg(test)]
559mod tests {
560 use super::*;
561
562 use crate::xml::xpath::types::NodeSet;
563
564 unsafe fn create_test_doc() -> *mut _xmlDoc {
570 let layout = std::alloc::Layout::new::<_xmlDoc>();
572 let ptr = std::alloc::alloc_zeroed(layout) as *mut _xmlDoc;
573 assert!(!ptr.is_null(), "failed to allocate test document");
574 ptr
575 }
576
577 unsafe fn create_test_node() -> *mut _xmlNode {
581 let layout = std::alloc::Layout::new::<_xmlNode>();
582 let ptr = std::alloc::alloc_zeroed(layout) as *mut _xmlNode;
583 assert!(!ptr.is_null(), "failed to allocate test node");
584 ptr
585 }
586
587 unsafe fn free_test_doc(doc: *mut _xmlDoc) {
589 if !doc.is_null() {
590 let layout = std::alloc::Layout::new::<_xmlDoc>();
591 std::alloc::dealloc(doc as *mut u8, layout);
592 }
593 }
594
595 unsafe fn free_test_node(node: *mut _xmlNode) {
597 if !node.is_null() {
598 let layout = std::alloc::Layout::new::<_xmlNode>();
599 std::alloc::dealloc(node as *mut u8, layout);
600 }
601 }
602
603 #[test]
606 fn test_new_context() {
607 let ctx = XPathContext::new(std::ptr::null_mut());
608 assert!(ctx.document.is_null());
609 assert!(ctx.context_node.is_null());
610 assert_eq!(ctx.context_position, 1);
611 assert_eq!(ctx.context_size, 1);
612 assert!(ctx.variables.is_empty());
613 assert!(ctx.namespaces.is_empty());
614 assert!(ctx.functions.is_empty());
615 assert!(ctx.error.is_none());
616 assert_eq!(ctx.proximity_position, 1);
617 assert!(ctx.context_list.is_empty());
618 assert_eq!(ctx.recursion_depth, 0);
619 assert!(ctx.var_lookup_func.is_none());
620 assert!(ctx.var_lookup_data.is_null());
621 assert!(ctx.func_lookup_func.is_none());
622 assert!(ctx.func_lookup_data.is_null());
623 }
624
625 #[test]
626 fn test_default_context() {
627 let ctx = XPathContext::default();
628 assert!(ctx.document.is_null());
629 assert_eq!(ctx.context_position, 1);
630 }
631
632 #[test]
633 fn test_new_with_doc() {
634 unsafe {
635 let doc = create_test_doc();
636 let ctx = XPathContext::new(doc);
637 assert_eq!(ctx.document, doc);
638 free_test_doc(doc);
639 }
640 }
641
642 #[test]
645 fn test_set_context_node_non_null() {
646 unsafe {
647 let node = create_test_node();
648 let mut ctx = XPathContext::new(std::ptr::null_mut());
649 ctx.set_context_node(node);
650
651 assert_eq!(ctx.context_node, node);
652 assert_eq!(ctx.context_position, 1);
653 assert_eq!(ctx.context_size, 1);
654 assert_eq!(ctx.proximity_position, 1);
655 assert_eq!(ctx.context_list.len(), 1);
656 assert_eq!(ctx.context_list[0], node);
657
658 free_test_node(node);
659 }
660 }
661
662 #[test]
663 fn test_set_context_node_null() {
664 let mut ctx = XPathContext::new(std::ptr::null_mut());
665 let sentinel = std::ptr::dangling_mut::<_xmlNode>();
668 ctx.context_list = vec![sentinel];
669 ctx.context_position = 5;
670 ctx.context_size = 5;
671 ctx.proximity_position = 5;
672
673 ctx.set_context_node(std::ptr::null_mut());
675 assert!(ctx.context_node.is_null());
676 assert!(ctx.context_list.is_empty());
677 assert_eq!(ctx.context_position, 1);
678 assert_eq!(ctx.context_size, 1);
679 assert_eq!(ctx.proximity_position, 1);
680 }
681
682 #[test]
685 fn test_set_context_list() {
686 unsafe {
687 let node1 = create_test_node();
688 let node2 = create_test_node();
689 let nodes = vec![node1, node2];
690
691 let mut ctx = XPathContext::new(std::ptr::null_mut());
692 ctx.set_context_list(nodes.clone());
693
694 assert_eq!(ctx.context_list.len(), 2);
695 assert_eq!(ctx.context_size, 2);
696 assert_eq!(ctx.context_position, 1);
697 assert_eq!(ctx.proximity_position, 1);
698
699 free_test_node(node1);
700 free_test_node(node2);
701 }
702 }
703
704 #[test]
705 fn test_set_context_list_empty() {
706 let mut ctx = XPathContext::new(std::ptr::null_mut());
707 ctx.set_context_list(vec![]);
708
709 assert!(ctx.context_list.is_empty());
710 assert_eq!(ctx.context_size, 0);
711 assert_eq!(ctx.context_position, 1);
712 }
713
714 #[test]
717 fn test_register_and_resolve_variable() {
718 let mut ctx = XPathContext::new(std::ptr::null_mut());
719 ctx.register_variable("foo", XPathValue::String("bar".to_string()));
720
721 let result = ctx.resolve_variable("foo");
722 assert!(result.is_some());
723 assert_eq!(result.unwrap().as_string(), "bar");
724 }
725
726 #[test]
727 fn test_resolve_unknown_variable() {
728 let ctx = XPathContext::new(std::ptr::null_mut());
729 assert!(ctx.resolve_variable("nonexistent").is_none());
730 }
731 #[allow(clippy::approx_constant)]
732 #[test]
733 fn test_register_variable_number() {
734 let mut ctx = XPathContext::new(std::ptr::null_mut());
735 ctx.register_variable("pi", XPathValue::Number(3.14159));
736
737 let result = ctx.resolve_variable("pi");
738 assert!(result.is_some());
739 let val = result.unwrap();
740 assert!((val.as_number() - 3.14159).abs() < 1e-10);
741 }
742
743 #[test]
744 fn test_register_variable_boolean() {
745 let mut ctx = XPathContext::new(std::ptr::null_mut());
746 ctx.register_variable("flag", XPathValue::Boolean(true));
747
748 let result = ctx.resolve_variable("flag");
749 assert!(result.is_some());
750 assert!(result.unwrap().as_boolean());
751 }
752
753 #[test]
754 fn test_register_variable_nodeset() {
755 let mut ctx = XPathContext::new(std::ptr::null_mut());
756 let ns = NodeSet::new();
757 ctx.register_variable("nodes", XPathValue::NodeSet(ns));
758
759 let result = ctx.resolve_variable("nodes");
760 assert!(result.is_some());
761 assert!(matches!(result.unwrap(), XPathValue::NodeSet(_)));
762 }
763
764 #[test]
765 fn test_variable_overwrite() {
766 let mut ctx = XPathContext::new(std::ptr::null_mut());
767 ctx.register_variable("x", XPathValue::Number(1.0));
768 ctx.register_variable("x", XPathValue::Number(2.0));
769
770 let result = ctx.resolve_variable("x");
771 assert!(result.is_some());
772 assert!((result.unwrap().as_number() - 2.0).abs() < 1e-10);
773 }
774
775 #[test]
778 fn test_register_and_resolve_namespace() {
779 let mut ctx = XPathContext::new(std::ptr::null_mut());
780 ctx.register_namespace("xslt", "http://www.w3.org/1999/XSL/Transform");
781
782 let result = ctx.resolve_namespace("xslt");
783 assert!(result.is_some());
784 assert_eq!(result.unwrap(), "http://www.w3.org/1999/XSL/Transform");
785 }
786
787 #[test]
788 fn test_resolve_unknown_namespace() {
789 let ctx = XPathContext::new(std::ptr::null_mut());
790 assert!(ctx.resolve_namespace("unknown").is_none());
792 }
793
794 #[test]
795 fn test_register_default_namespace() {
796 let mut ctx = XPathContext::new(std::ptr::null_mut());
797 ctx.register_namespace("", "http://example.com/default");
798
799 let result = ctx.resolve_namespace("");
800 assert!(result.is_some());
801 assert_eq!(result.unwrap(), "http://example.com/default");
802 }
803
804 #[test]
805 fn test_namespace_overwrite() {
806 let mut ctx = XPathContext::new(std::ptr::null_mut());
807 ctx.register_namespace("a", "http://example.com/1");
808 ctx.register_namespace("a", "http://example.com/2");
809
810 let result = ctx.resolve_namespace("a");
811 assert_eq!(result.unwrap(), "http://example.com/2");
812 }
813
814 #[test]
817 fn test_register_and_lookup_function() {
818 fn test_func(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
819 Ok(XPathValue::String("test".to_string()))
820 }
821
822 let mut ctx = XPathContext::new(std::ptr::null_mut());
823 ctx.register_function("test:func", test_func);
824
825 let result = ctx.lookup_function("test:func");
826 assert!(result.is_some());
827 }
828
829 #[test]
830 fn test_lookup_unknown_function() {
831 let mut ctx = XPathContext::new(std::ptr::null_mut());
832 assert!(ctx.lookup_function("nonexistent").is_none());
833 }
834
835 #[test]
836 fn test_function_overwrite() {
837 fn func_a(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
838 Ok(XPathValue::String("a".to_string()))
839 }
840 fn func_b(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
841 Ok(XPathValue::String("b".to_string()))
842 }
843
844 let mut ctx = XPathContext::new(std::ptr::null_mut());
845 ctx.register_function("f", func_a);
846 ctx.register_function("f", func_b);
847
848 let result = ctx.lookup_function("f");
849 assert!(result.is_some());
850
851 if let Some(f) = result {
853 let mut tmp_ctx = XPathContext::new(std::ptr::null_mut());
854 let value = f(&mut tmp_ctx, &[]).unwrap();
855 assert_eq!(value.as_string(), "b");
856 }
857 }
858
859 #[test]
862 fn test_set_and_get_error() {
863 let mut ctx = XPathContext::new(std::ptr::null_mut());
864 assert!(ctx.error.is_none());
865
866 ctx.set_error("something went wrong");
867 assert_eq!(ctx.error.as_deref(), Some("something went wrong"));
868 }
869
870 #[test]
871 fn test_clear_error() {
872 let mut ctx = XPathContext::new(std::ptr::null_mut());
873 ctx.set_error("an error");
874 assert!(ctx.error.is_some());
875
876 ctx.clear_error();
877 assert!(ctx.error.is_none());
878 }
879
880 #[test]
881 fn test_error_overwrite() {
882 let mut ctx = XPathContext::new(std::ptr::null_mut());
883 ctx.set_error("first error");
884 ctx.set_error("second error");
885 assert_eq!(ctx.error.as_deref(), Some("second error"));
886 }
887
888 #[test]
891 fn test_push_pop_recursion() {
892 let mut ctx = XPathContext::new(std::ptr::null_mut());
893 assert_eq!(ctx.recursion_depth, 0);
894
895 assert!(ctx.push_recursion().is_ok());
896 assert_eq!(ctx.recursion_depth, 1);
897
898 ctx.pop_recursion();
899 assert_eq!(ctx.recursion_depth, 0);
900 }
901
902 #[test]
903 fn test_recursion_depth_limit() {
904 let mut ctx = XPathContext::new(std::ptr::null_mut());
905
906 for _ in 0..1000 {
908 assert!(ctx.push_recursion().is_ok());
909 }
910 assert_eq!(ctx.recursion_depth, 1000);
911
912 let result = ctx.push_recursion();
914 assert!(result.is_err());
915 assert!(result.unwrap_err().contains("recursion depth exceeded"));
916
917 for _ in 0..1000 {
919 ctx.pop_recursion();
920 }
921 assert_eq!(ctx.recursion_depth, 0);
922 }
923
924 #[test]
925 #[should_panic(expected = "unbalanced pop_recursion")]
926 fn test_pop_recursion_underflow() {
927 let mut ctx = XPathContext::new(std::ptr::null_mut());
928 ctx.pop_recursion(); }
930
931 #[test]
932 fn test_recursion_nesting() {
933 let mut ctx = XPathContext::new(std::ptr::null_mut());
934
935 assert!(ctx.push_recursion().is_ok());
937 assert!(ctx.push_recursion().is_ok());
938 assert!(ctx.push_recursion().is_ok());
939 assert_eq!(ctx.recursion_depth, 3);
940
941 ctx.pop_recursion();
942 assert_eq!(ctx.recursion_depth, 2);
943
944 ctx.pop_recursion();
945 assert_eq!(ctx.recursion_depth, 1);
946
947 ctx.pop_recursion();
948 assert_eq!(ctx.recursion_depth, 0);
949 }
950
951 #[test]
954 fn test_position_and_last() {
955 let ctx = XPathContext::new(std::ptr::null_mut());
956 assert_eq!(ctx.position(), 1);
957 assert_eq!(ctx.last(), 1);
958 }
959
960 #[test]
961 fn test_advance_position() {
962 let mut ctx = XPathContext::new(std::ptr::null_mut());
963 ctx.advance_position();
964 assert_eq!(ctx.position(), 2);
965 assert_eq!(ctx.proximity_position, 2);
966 assert_eq!(ctx.context_position, 2);
967 }
968
969 #[test]
970 fn test_reset_position() {
971 let mut ctx = XPathContext::new(std::ptr::null_mut());
972 ctx.advance_position();
973 ctx.advance_position();
974 ctx.advance_position();
975 assert_eq!(ctx.position(), 4);
976
977 ctx.reset_position();
978 assert_eq!(ctx.position(), 1);
979 assert_eq!(ctx.context_position, 1);
980 }
981
982 #[test]
983 fn test_position_with_context_list() {
984 unsafe {
985 let node1 = create_test_node();
986 let node2 = create_test_node();
987 let node3 = create_test_node();
988 let nodes = vec![node1, node2, node3];
989
990 let mut ctx = XPathContext::new(std::ptr::null_mut());
991 ctx.set_context_list(nodes);
992
993 assert_eq!(ctx.last(), 3);
994 assert_eq!(ctx.position(), 1);
995
996 ctx.advance_position();
997 assert_eq!(ctx.position(), 2);
998
999 ctx.advance_position();
1000 assert_eq!(ctx.position(), 3);
1001
1002 free_test_node(node1);
1003 free_test_node(node2);
1004 free_test_node(node3);
1005 }
1006 }
1007
1008 #[test]
1011 fn test_has_context_node() {
1012 let mut ctx = XPathContext::new(std::ptr::null_mut());
1013 assert!(!ctx.has_context_node());
1014
1015 unsafe {
1016 let node = create_test_node();
1017 ctx.set_context_node(node);
1018 assert!(ctx.has_context_node());
1019 free_test_node(node);
1020 }
1021 }
1022
1023 #[test]
1026 fn test_reset() {
1027 let mut ctx = XPathContext::new(std::ptr::null_mut());
1028
1029 ctx.set_error("test error");
1031 ctx.proximity_position = 5;
1032 ctx.context_position = 5;
1033 ctx.context_size = 10;
1034 ctx.recursion_depth = 3;
1035 {
1036 let sentinel = std::ptr::dangling_mut::<_xmlNode>();
1037 ctx.context_list = vec![sentinel];
1038 }
1039
1040 ctx.register_variable("x", XPathValue::Number(42.0));
1042 ctx.register_namespace("p", "http://example.com/ns");
1043 fn dummy(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1044 Ok(XPathValue::Boolean(true))
1045 }
1046 ctx.register_function("f", dummy);
1047
1048 ctx.reset();
1049
1050 assert!(ctx.context_node.is_null());
1052 assert_eq!(ctx.context_position, 1);
1053 assert_eq!(ctx.context_size, 1);
1054 assert_eq!(ctx.proximity_position, 1);
1055 assert!(ctx.error.is_none());
1056 assert!(ctx.context_list.is_empty());
1057 assert_eq!(ctx.recursion_depth, 0);
1058
1059 assert!(ctx.resolve_variable("x").is_some());
1061 assert!(ctx.resolve_namespace("p").is_some());
1062 assert!(ctx.lookup_function("f").is_some());
1063 }
1064
1065 #[test]
1068 fn test_callback_fields_default_to_none() {
1069 let ctx = XPathContext::new(std::ptr::null_mut());
1070 assert!(ctx.var_lookup_func.is_none());
1071 assert!(ctx.var_lookup_data.is_null());
1072 assert!(ctx.func_lookup_func.is_none());
1073 assert!(ctx.func_lookup_data.is_null());
1074 }
1075
1076 #[test]
1077 fn test_set_callback_fields() {
1078 let mut ctx = XPathContext::new(std::ptr::null_mut());
1079
1080 unsafe extern "C" fn dummy_var_lookup(
1081 _data: *mut c_void,
1082 _ns: *const xmlChar,
1083 _name: *const xmlChar,
1084 ) -> *mut _xmlXPathObject {
1085 std::ptr::null_mut()
1086 }
1087
1088 unsafe extern "C" fn dummy_func_lookup(
1089 _data: *mut c_void,
1090 _ns: *const xmlChar,
1091 _name: *const xmlChar,
1092 ) -> *mut c_void {
1093 std::ptr::null_mut()
1094 }
1095
1096 let data_ptr = &mut 42u32 as *mut u32 as *mut c_void;
1097
1098 ctx.var_lookup_func = Some(dummy_var_lookup);
1099 ctx.var_lookup_data = data_ptr;
1100 ctx.func_lookup_func = Some(dummy_func_lookup);
1101 ctx.func_lookup_data = data_ptr;
1102
1103 assert!(ctx.var_lookup_func.is_some());
1104 assert!(!ctx.var_lookup_data.is_null());
1105 assert!(ctx.func_lookup_func.is_some());
1106 assert!(!ctx.func_lookup_data.is_null());
1107 }
1108
1109 #[test]
1112 fn test_context_clone() {
1113 let mut ctx = XPathContext::new(std::ptr::null_mut());
1114 ctx.register_variable("x", XPathValue::Number(10.0));
1115 ctx.register_namespace("ns", "http://example.com/ns");
1116 ctx.set_error("clone test");
1117
1118 let cloned = ctx.clone();
1119 assert_eq!(cloned.document, ctx.document);
1120 assert_eq!(cloned.context_node, ctx.context_node);
1121 assert_eq!(cloned.context_position, ctx.context_position);
1122 assert_eq!(cloned.context_size, ctx.context_size);
1123 assert_eq!(cloned.error, ctx.error);
1124
1125 let var = cloned.resolve_variable("x");
1127 assert!(var.is_some());
1128 assert!((var.unwrap().as_number() - 10.0).abs() < 1e-10);
1129
1130 let ns = cloned.resolve_namespace("ns");
1131 assert!(ns.is_some());
1132 assert_eq!(ns.unwrap(), "http://example.com/ns");
1133 }
1134
1135 #[test]
1138 fn test_context_debug_format() {
1139 let ctx = XPathContext::new(std::ptr::null_mut());
1140 let debug_str = format!("{:?}", ctx);
1141 assert!(debug_str.contains("context_position"));
1142 assert!(debug_str.contains("context_size"));
1143 assert!(debug_str.contains("recursion_depth"));
1144 }
1145
1146 #[test]
1149 fn test_context_size_zero() {
1150 let mut ctx = XPathContext::new(std::ptr::null_mut());
1151 ctx.set_context_list(vec![]);
1152 assert_eq!(ctx.last(), 0);
1153 assert_eq!(ctx.position(), 1);
1154 }
1155
1156 #[test]
1157 fn test_multiple_advancements() {
1158 let mut ctx = XPathContext::new(std::ptr::null_mut());
1159 for i in 1..=10 {
1160 assert_eq!(ctx.position(), i);
1161 ctx.advance_position();
1162 }
1163 assert_eq!(ctx.position(), 11);
1164 }
1165
1166 #[test]
1167 fn test_register_multiple_variables() {
1168 let mut ctx = XPathContext::new(std::ptr::null_mut());
1169 ctx.register_variable("a", XPathValue::Number(1.0));
1170 ctx.register_variable("b", XPathValue::String("two".to_string()));
1171 ctx.register_variable("c", XPathValue::Boolean(true));
1172
1173 assert_eq!(ctx.variables.len(), 3);
1174 assert_eq!(ctx.resolve_variable("a").unwrap().as_number(), 1.0);
1175 assert_eq!(ctx.resolve_variable("b").unwrap().as_string(), "two");
1176 assert!(ctx.resolve_variable("c").unwrap().as_boolean());
1177 }
1178
1179 #[test]
1180 fn test_register_multiple_namespaces() {
1181 let mut ctx = XPathContext::new(std::ptr::null_mut());
1182 ctx.register_namespace("a", "http://example.com/a");
1183 ctx.register_namespace("b", "http://example.com/b");
1184 ctx.register_namespace("c", "http://example.com/c");
1185
1186 assert_eq!(ctx.namespaces.len(), 3);
1187 assert_eq!(ctx.resolve_namespace("a").unwrap(), "http://example.com/a");
1188 assert_eq!(ctx.resolve_namespace("b").unwrap(), "http://example.com/b");
1189 assert_eq!(ctx.resolve_namespace("c").unwrap(), "http://example.com/c");
1190 }
1191
1192 #[test]
1193 fn test_register_multiple_functions() {
1194 fn f1(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1195 Ok(XPathValue::Number(1.0))
1196 }
1197 fn f2(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1198 Ok(XPathValue::Number(2.0))
1199 }
1200 fn f3(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1201 Ok(XPathValue::Number(3.0))
1202 }
1203
1204 let mut ctx = XPathContext::new(std::ptr::null_mut());
1205 ctx.register_function("f1", f1);
1206 ctx.register_function("f2", f2);
1207 ctx.register_function("f3", f3);
1208
1209 assert_eq!(ctx.functions.len(), 3);
1210 }
1211}