1use crate::abi::structs::{_xmlDoc, _xmlNode, _xmlXPathObject};
73use crate::abi::types::xmlChar;
74use crate::xml::xpath::types::XPathValue;
75use std::collections::HashMap;
76use std::os::raw::c_void;
77
78pub type XPathFunction = fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String>;
88
89pub type BoxedXPathFunction =
95 Box<dyn Fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String> + Send + Sync>;
96
97pub type FunctionLookupFn =
101 Box<dyn Fn(&XPathContext, &str) -> Option<BoxedXPathFunction> + Send + Sync>;
102
103pub type VarLookupFunc =
116 unsafe extern "C" fn(*mut c_void, *const xmlChar, *const xmlChar) -> *mut _xmlXPathObject;
117
118pub type FuncLookupFunc =
131 unsafe extern "C" fn(*mut c_void, *const xmlChar, *const xmlChar) -> *mut c_void;
132
133pub struct XPathContext {
155 pub document: *mut _xmlDoc,
157
158 pub context_node: *mut _xmlNode,
160
161 pub context_position: i32,
163
164 pub context_size: i32,
166
167 pub variables: HashMap<String, XPathValue>,
169
170 pub namespaces: HashMap<String, String>,
172
173 pub functions: HashMap<String, BoxedXPathFunction>,
175
176 pub function_lookup: Option<FunctionLookupFn>,
179
180 pub error: Option<String>,
182
183 pub proximity_position: i32,
185
186 pub context_list: Vec<*mut _xmlNode>,
188
189 pub recursion_depth: u32,
191
192 pub var_lookup_func: Option<VarLookupFunc>,
194
195 pub var_lookup_data: *mut c_void,
197
198 pub func_lookup_func: Option<FuncLookupFunc>,
200
201 pub func_lookup_data: *mut c_void,
203}
204
205impl std::fmt::Debug for XPathContext {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 let names: Vec<&String> = self.functions.keys().collect();
210 f.debug_struct("XPathContext")
211 .field("document", &self.document)
212 .field("context_node", &self.context_node)
213 .field("context_position", &self.context_position)
214 .field("context_size", &self.context_size)
215 .field("variables", &self.variables)
216 .field("namespaces", &self.namespaces)
217 .field("functions", &names)
218 .field("error", &self.error)
219 .field("recursion_depth", &self.recursion_depth)
220 .finish()
221 }
222}
223
224impl Clone for XPathContext {
225 fn clone(&self) -> Self {
226 let mut cloned = XPathContext::new(self.document);
229 cloned.context_node = self.context_node;
230 cloned.context_position = self.context_position;
231 cloned.context_size = self.context_size;
232 cloned.variables = self.variables.clone();
233 cloned.namespaces = self.namespaces.clone();
234 cloned.error = self.error.clone();
235 cloned.proximity_position = self.proximity_position;
236 cloned.context_list = self.context_list.clone();
237 cloned.recursion_depth = self.recursion_depth;
238 cloned.var_lookup_func = self.var_lookup_func;
239 cloned.var_lookup_data = self.var_lookup_data;
240 cloned.func_lookup_func = self.func_lookup_func;
241 cloned.func_lookup_data = self.func_lookup_data;
242 cloned
243 }
244}
245
246impl XPathContext {
247 pub fn new(doc: *mut _xmlDoc) -> Self {
261 Self {
262 document: doc,
263 context_node: std::ptr::null_mut(),
264 context_position: 1,
265 context_size: 1,
266 variables: HashMap::new(),
267 namespaces: HashMap::new(),
268 functions: HashMap::new(),
269 function_lookup: None,
270 error: None,
271 proximity_position: 1,
272 context_list: Vec::new(),
273 recursion_depth: 0,
274 var_lookup_func: None,
275 var_lookup_data: std::ptr::null_mut(),
276 func_lookup_func: None,
277 func_lookup_data: std::ptr::null_mut(),
278 }
279 }
280
281 pub fn set_context_node(&mut self, node: *mut _xmlNode) {
290 self.context_node = node;
291 if node.is_null() {
292 self.context_list.clear();
293 self.context_position = 1;
294 self.context_size = 1;
295 self.proximity_position = 1;
296 } else {
297 self.context_list = vec![node];
298 self.context_position = 1;
299 self.context_size = 1;
300 self.proximity_position = 1;
301 }
302 }
303
304 pub fn set_context_list(&mut self, nodes: Vec<*mut _xmlNode>) {
312 self.context_size = nodes.len() as i32;
313 self.context_list = nodes;
314 self.context_position = 1;
315 self.proximity_position = 1;
316 }
317
318 pub fn resolve_variable(&self, name: &str) -> Option<XPathValue> {
334 if let Some(value) = self.variables.get(name) {
336 return Some(value.clone());
337 }
338
339 if let Some(lookup) = self.var_lookup_func {
341 let c_name: Vec<xmlChar> = name.bytes().collect();
343 let result = unsafe { lookup(self.var_lookup_data, std::ptr::null(), c_name.as_ptr()) };
346 if !result.is_null() {
347 {
352 let _ = result; }
357 }
358 }
359
360 None
361 }
362
363 pub fn resolve_namespace(&self, prefix: &str) -> Option<String> {
371 if let Some(uri) = self.namespaces.get(prefix) {
373 return Some(uri.clone());
374 }
375
376 let mut current = self.context_node;
379 while !current.is_null() {
380 unsafe {
383 let mut ns = (*current).nsDef;
384 while !ns.is_null() {
385 let ns_prefix = (*ns).prefix;
386 let ns_href = (*ns).href;
387
388 let prefix_matches = if ns_prefix.is_null() {
390 prefix.is_empty()
393 } else {
394 let mut len = 0;
396 while *ns_prefix.add(len) != 0 {
397 len += 1;
398 }
399 let slice = std::slice::from_raw_parts(ns_prefix, len);
400 slice == prefix.as_bytes()
401 };
402
403 if prefix_matches {
404 let mut len = 0;
406 while *ns_href.add(len) != 0 {
407 len += 1;
408 }
409 let slice = std::slice::from_raw_parts(ns_href, len);
410 return Some(String::from_utf8_lossy(slice).into_owned());
411 }
412
413 ns = (*ns).next;
414 }
415 }
416
417 unsafe {
420 current = (*current).parent;
421 }
422 }
423
424 None
425 }
426
427 pub fn lookup_function(&mut self, name: &str) -> Option<&BoxedXPathFunction> {
434 if self.functions.contains_key(name) {
436 return self.functions.get(name);
437 }
438
439 if let Some(lookup) = &self.function_lookup {
442 if let Some(func) = lookup(self, name) {
443 self.functions.insert(name.to_string(), func);
444 return self.functions.get(name);
445 }
446 }
447
448 if let Some(lookup) = self.func_lookup_func {
450 let c_name: Vec<xmlChar> = name.bytes().collect();
451 let _result =
453 unsafe { lookup(self.func_lookup_data, std::ptr::null(), c_name.as_ptr()) };
454 }
458
459 None
460 }
461
462 pub fn register_function<F>(&mut self, name: &str, func: F)
469 where
470 F: Fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String>
471 + Send
472 + Sync
473 + 'static,
474 {
475 self.functions.insert(name.to_string(), Box::new(func));
476 }
477
478 pub fn register_variable(&mut self, name: &str, value: XPathValue) {
484 self.variables.insert(name.to_string(), value);
485 }
486
487 pub fn unregister_variable(&mut self, name: &str) {
492 self.variables.remove(name);
493 }
494
495 pub fn register_namespace(&mut self, prefix: &str, uri: &str) {
500 self.namespaces.insert(prefix.to_string(), uri.to_string());
501 }
502
503 pub fn set_error(&mut self, msg: &str) {
507 self.error = Some(msg.to_string());
508 }
509
510 pub fn clear_error(&mut self) {
512 self.error = None;
513 }
514
515 pub fn push_recursion(&mut self) -> Result<(), String> {
523 const MAX_RECURSION_DEPTH: u32 = 1000;
524 if self.recursion_depth >= MAX_RECURSION_DEPTH {
525 return Err(
526 "XPath evaluation recursion depth exceeded (infinite recursion?)".to_string(),
527 );
528 }
529 self.recursion_depth += 1;
530 Ok(())
531 }
532
533 pub fn pop_recursion(&mut self) {
543 assert!(
544 self.recursion_depth > 0,
545 "unbalanced pop_recursion: recursion_depth is already 0"
546 );
547 self.recursion_depth -= 1;
548 }
549
550 pub const fn has_context_node(&self) -> bool {
552 !self.context_node.is_null()
553 }
554
555 pub fn reset(&mut self) {
560 self.context_node = std::ptr::null_mut();
561 self.context_position = 1;
562 self.context_size = 1;
563 self.error = None;
564 self.proximity_position = 1;
565 self.context_list.clear();
566 self.recursion_depth = 0;
567 }
568
569 pub const fn position(&self) -> i32 {
573 self.proximity_position
574 }
575
576 pub const fn last(&self) -> i32 {
580 self.context_size
581 }
582
583 pub const fn advance_position(&mut self) {
588 self.proximity_position += 1;
589 self.context_position = self.proximity_position;
590 }
591
592 pub const fn reset_position(&mut self) {
594 self.proximity_position = 1;
595 self.context_position = 1;
596 }
597}
598
599impl Default for XPathContext {
600 fn default() -> Self {
605 Self::new(std::ptr::null_mut())
606 }
607}
608
609#[cfg(test)]
614mod tests {
615 use super::*;
616
617 use crate::xml::xpath::types::NodeSet;
618
619 unsafe fn create_test_doc() -> *mut _xmlDoc {
625 let layout = std::alloc::Layout::new::<_xmlDoc>();
627 let ptr = std::alloc::alloc_zeroed(layout) as *mut _xmlDoc;
628 assert!(!ptr.is_null(), "failed to allocate test document");
629 ptr
630 }
631
632 unsafe fn create_test_node() -> *mut _xmlNode {
636 let layout = std::alloc::Layout::new::<_xmlNode>();
637 let ptr = std::alloc::alloc_zeroed(layout) as *mut _xmlNode;
638 assert!(!ptr.is_null(), "failed to allocate test node");
639 ptr
640 }
641
642 unsafe fn free_test_doc(doc: *mut _xmlDoc) {
644 if !doc.is_null() {
645 let layout = std::alloc::Layout::new::<_xmlDoc>();
646 std::alloc::dealloc(doc as *mut u8, layout);
647 }
648 }
649
650 unsafe fn free_test_node(node: *mut _xmlNode) {
652 if !node.is_null() {
653 let layout = std::alloc::Layout::new::<_xmlNode>();
654 std::alloc::dealloc(node as *mut u8, layout);
655 }
656 }
657
658 #[test]
661 fn test_new_context() {
662 let ctx = XPathContext::new(std::ptr::null_mut());
663 assert!(ctx.document.is_null());
664 assert!(ctx.context_node.is_null());
665 assert_eq!(ctx.context_position, 1);
666 assert_eq!(ctx.context_size, 1);
667 assert!(ctx.variables.is_empty());
668 assert!(ctx.namespaces.is_empty());
669 assert!(ctx.functions.is_empty());
670 assert!(ctx.error.is_none());
671 assert_eq!(ctx.proximity_position, 1);
672 assert!(ctx.context_list.is_empty());
673 assert_eq!(ctx.recursion_depth, 0);
674 assert!(ctx.var_lookup_func.is_none());
675 assert!(ctx.var_lookup_data.is_null());
676 assert!(ctx.func_lookup_func.is_none());
677 assert!(ctx.func_lookup_data.is_null());
678 }
679
680 #[test]
681 fn test_default_context() {
682 let ctx = XPathContext::default();
683 assert!(ctx.document.is_null());
684 assert_eq!(ctx.context_position, 1);
685 }
686
687 #[test]
695 fn test_new_with_doc() {
696 unsafe {
697 let doc = create_test_doc();
698 let ctx = XPathContext::new(doc);
699 assert_eq!(ctx.document, doc);
700 free_test_doc(doc);
701 }
702 }
703
704 #[test]
714 fn test_set_context_node_non_null() {
715 unsafe {
716 let node = create_test_node();
717 let mut ctx = XPathContext::new(std::ptr::null_mut());
718 ctx.set_context_node(node);
719
720 assert_eq!(ctx.context_node, node);
721 assert_eq!(ctx.context_position, 1);
722 assert_eq!(ctx.context_size, 1);
723 assert_eq!(ctx.proximity_position, 1);
724 assert_eq!(ctx.context_list.len(), 1);
725 assert_eq!(ctx.context_list[0], node);
726
727 free_test_node(node);
728 }
729 }
730
731 #[test]
732 fn test_set_context_node_null() {
733 let mut ctx = XPathContext::new(std::ptr::null_mut());
734 let sentinel = std::ptr::dangling_mut::<_xmlNode>();
737 ctx.context_list = vec![sentinel];
738 ctx.context_position = 5;
739 ctx.context_size = 5;
740 ctx.proximity_position = 5;
741
742 ctx.set_context_node(std::ptr::null_mut());
744 assert!(ctx.context_node.is_null());
745 assert!(ctx.context_list.is_empty());
746 assert_eq!(ctx.context_position, 1);
747 assert_eq!(ctx.context_size, 1);
748 assert_eq!(ctx.proximity_position, 1);
749 }
750
751 #[test]
761 fn test_set_context_list() {
762 unsafe {
763 let node1 = create_test_node();
764 let node2 = create_test_node();
765 let nodes = vec![node1, node2];
766
767 let mut ctx = XPathContext::new(std::ptr::null_mut());
768 ctx.set_context_list(nodes.clone());
769
770 assert_eq!(ctx.context_list.len(), 2);
771 assert_eq!(ctx.context_size, 2);
772 assert_eq!(ctx.context_position, 1);
773 assert_eq!(ctx.proximity_position, 1);
774
775 free_test_node(node1);
776 free_test_node(node2);
777 }
778 }
779
780 #[test]
781 fn test_set_context_list_empty() {
782 let mut ctx = XPathContext::new(std::ptr::null_mut());
783 ctx.set_context_list(vec![]);
784
785 assert!(ctx.context_list.is_empty());
786 assert_eq!(ctx.context_size, 0);
787 assert_eq!(ctx.context_position, 1);
788 }
789
790 #[test]
793 fn test_register_and_resolve_variable() {
794 let mut ctx = XPathContext::new(std::ptr::null_mut());
795 ctx.register_variable("foo", XPathValue::String("bar".to_string()));
796
797 let result = ctx.resolve_variable("foo");
798 assert!(result.is_some());
799 assert_eq!(result.unwrap().as_string(), "bar");
800 }
801
802 #[test]
803 fn test_resolve_unknown_variable() {
804 let ctx = XPathContext::new(std::ptr::null_mut());
805 assert!(ctx.resolve_variable("nonexistent").is_none());
806 }
807 #[allow(clippy::approx_constant)]
808 #[test]
809 fn test_register_variable_number() {
810 let mut ctx = XPathContext::new(std::ptr::null_mut());
811 ctx.register_variable("pi", XPathValue::Number(3.14159));
812
813 let result = ctx.resolve_variable("pi");
814 assert!(result.is_some());
815 let val = result.unwrap();
816 assert!((val.as_number() - 3.14159).abs() < 1e-10);
817 }
818
819 #[test]
820 fn test_register_variable_boolean() {
821 let mut ctx = XPathContext::new(std::ptr::null_mut());
822 ctx.register_variable("flag", XPathValue::Boolean(true));
823
824 let result = ctx.resolve_variable("flag");
825 assert!(result.is_some());
826 assert!(result.unwrap().as_boolean());
827 }
828
829 #[test]
830 fn test_register_variable_nodeset() {
831 let mut ctx = XPathContext::new(std::ptr::null_mut());
832 let ns = NodeSet::new();
833 ctx.register_variable("nodes", XPathValue::NodeSet(ns));
834
835 let result = ctx.resolve_variable("nodes");
836 assert!(result.is_some());
837 assert!(matches!(result.unwrap(), XPathValue::NodeSet(_)));
838 }
839
840 #[test]
841 fn test_variable_overwrite() {
842 let mut ctx = XPathContext::new(std::ptr::null_mut());
843 ctx.register_variable("x", XPathValue::Number(1.0));
844 ctx.register_variable("x", XPathValue::Number(2.0));
845
846 let result = ctx.resolve_variable("x");
847 assert!(result.is_some());
848 assert!((result.unwrap().as_number() - 2.0).abs() < 1e-10);
849 }
850
851 #[test]
854 fn test_register_and_resolve_namespace() {
855 let mut ctx = XPathContext::new(std::ptr::null_mut());
856 ctx.register_namespace("xslt", "http://www.w3.org/1999/XSL/Transform");
857
858 let result = ctx.resolve_namespace("xslt");
859 assert!(result.is_some());
860 assert_eq!(result.unwrap(), "http://www.w3.org/1999/XSL/Transform");
861 }
862
863 #[test]
864 fn test_resolve_unknown_namespace() {
865 let ctx = XPathContext::new(std::ptr::null_mut());
866 assert!(ctx.resolve_namespace("unknown").is_none());
868 }
869
870 #[test]
871 fn test_register_default_namespace() {
872 let mut ctx = XPathContext::new(std::ptr::null_mut());
873 ctx.register_namespace("", "http://example.com/default");
874
875 let result = ctx.resolve_namespace("");
876 assert!(result.is_some());
877 assert_eq!(result.unwrap(), "http://example.com/default");
878 }
879
880 #[test]
881 fn test_namespace_overwrite() {
882 let mut ctx = XPathContext::new(std::ptr::null_mut());
883 ctx.register_namespace("a", "http://example.com/1");
884 ctx.register_namespace("a", "http://example.com/2");
885
886 let result = ctx.resolve_namespace("a");
887 assert_eq!(result.unwrap(), "http://example.com/2");
888 }
889
890 #[test]
893 fn test_register_and_lookup_function() {
894 fn test_func(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
895 Ok(XPathValue::String("test".to_string()))
896 }
897
898 let mut ctx = XPathContext::new(std::ptr::null_mut());
899 ctx.register_function("test:func", test_func);
900
901 let result = ctx.lookup_function("test:func");
902 assert!(result.is_some());
903 }
904
905 #[test]
906 fn test_lookup_unknown_function() {
907 let mut ctx = XPathContext::new(std::ptr::null_mut());
908 assert!(ctx.lookup_function("nonexistent").is_none());
909 }
910
911 #[test]
912 fn test_function_overwrite() {
913 fn func_a(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
914 Ok(XPathValue::String("a".to_string()))
915 }
916 fn func_b(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
917 Ok(XPathValue::String("b".to_string()))
918 }
919
920 let mut ctx = XPathContext::new(std::ptr::null_mut());
921 ctx.register_function("f", func_a);
922 ctx.register_function("f", func_b);
923
924 let result = ctx.lookup_function("f");
925 assert!(result.is_some());
926
927 if let Some(f) = result {
929 let mut tmp_ctx = XPathContext::new(std::ptr::null_mut());
930 let value = f(&mut tmp_ctx, &[]).unwrap();
931 assert_eq!(value.as_string(), "b");
932 }
933 }
934
935 #[test]
938 fn test_set_and_get_error() {
939 let mut ctx = XPathContext::new(std::ptr::null_mut());
940 assert!(ctx.error.is_none());
941
942 ctx.set_error("something went wrong");
943 assert_eq!(ctx.error.as_deref(), Some("something went wrong"));
944 }
945
946 #[test]
947 fn test_clear_error() {
948 let mut ctx = XPathContext::new(std::ptr::null_mut());
949 ctx.set_error("an error");
950 assert!(ctx.error.is_some());
951
952 ctx.clear_error();
953 assert!(ctx.error.is_none());
954 }
955
956 #[test]
957 fn test_error_overwrite() {
958 let mut ctx = XPathContext::new(std::ptr::null_mut());
959 ctx.set_error("first error");
960 ctx.set_error("second error");
961 assert_eq!(ctx.error.as_deref(), Some("second error"));
962 }
963
964 #[test]
967 fn test_push_pop_recursion() {
968 let mut ctx = XPathContext::new(std::ptr::null_mut());
969 assert_eq!(ctx.recursion_depth, 0);
970
971 assert!(ctx.push_recursion().is_ok());
972 assert_eq!(ctx.recursion_depth, 1);
973
974 ctx.pop_recursion();
975 assert_eq!(ctx.recursion_depth, 0);
976 }
977
978 #[test]
979 fn test_recursion_depth_limit() {
980 let mut ctx = XPathContext::new(std::ptr::null_mut());
981
982 for _ in 0..1000 {
984 assert!(ctx.push_recursion().is_ok());
985 }
986 assert_eq!(ctx.recursion_depth, 1000);
987
988 let result = ctx.push_recursion();
990 assert!(result.is_err());
991 assert!(result.unwrap_err().contains("recursion depth exceeded"));
992
993 for _ in 0..1000 {
995 ctx.pop_recursion();
996 }
997 assert_eq!(ctx.recursion_depth, 0);
998 }
999
1000 #[test]
1001 #[should_panic(expected = "unbalanced pop_recursion")]
1002 fn test_pop_recursion_underflow() {
1003 let mut ctx = XPathContext::new(std::ptr::null_mut());
1004 ctx.pop_recursion(); }
1006
1007 #[test]
1008 fn test_recursion_nesting() {
1009 let mut ctx = XPathContext::new(std::ptr::null_mut());
1010
1011 assert!(ctx.push_recursion().is_ok());
1013 assert!(ctx.push_recursion().is_ok());
1014 assert!(ctx.push_recursion().is_ok());
1015 assert_eq!(ctx.recursion_depth, 3);
1016
1017 ctx.pop_recursion();
1018 assert_eq!(ctx.recursion_depth, 2);
1019
1020 ctx.pop_recursion();
1021 assert_eq!(ctx.recursion_depth, 1);
1022
1023 ctx.pop_recursion();
1024 assert_eq!(ctx.recursion_depth, 0);
1025 }
1026
1027 #[test]
1030 fn test_position_and_last() {
1031 let ctx = XPathContext::new(std::ptr::null_mut());
1032 assert_eq!(ctx.position(), 1);
1033 assert_eq!(ctx.last(), 1);
1034 }
1035
1036 #[test]
1037 fn test_advance_position() {
1038 let mut ctx = XPathContext::new(std::ptr::null_mut());
1039 ctx.advance_position();
1040 assert_eq!(ctx.position(), 2);
1041 assert_eq!(ctx.proximity_position, 2);
1042 assert_eq!(ctx.context_position, 2);
1043 }
1044
1045 #[test]
1046 fn test_reset_position() {
1047 let mut ctx = XPathContext::new(std::ptr::null_mut());
1048 ctx.advance_position();
1049 ctx.advance_position();
1050 ctx.advance_position();
1051 assert_eq!(ctx.position(), 4);
1052
1053 ctx.reset_position();
1054 assert_eq!(ctx.position(), 1);
1055 assert_eq!(ctx.context_position, 1);
1056 }
1057
1058 #[test]
1066 fn test_position_with_context_list() {
1067 unsafe {
1068 let node1 = create_test_node();
1069 let node2 = create_test_node();
1070 let node3 = create_test_node();
1071 let nodes = vec![node1, node2, node3];
1072
1073 let mut ctx = XPathContext::new(std::ptr::null_mut());
1074 ctx.set_context_list(nodes);
1075
1076 assert_eq!(ctx.last(), 3);
1077 assert_eq!(ctx.position(), 1);
1078
1079 ctx.advance_position();
1080 assert_eq!(ctx.position(), 2);
1081
1082 ctx.advance_position();
1083 assert_eq!(ctx.position(), 3);
1084
1085 free_test_node(node1);
1086 free_test_node(node2);
1087 free_test_node(node3);
1088 }
1089 }
1090
1091 #[test]
1101 fn test_has_context_node() {
1102 let mut ctx = XPathContext::new(std::ptr::null_mut());
1103 assert!(!ctx.has_context_node());
1104
1105 unsafe {
1106 let node = create_test_node();
1107 ctx.set_context_node(node);
1108 assert!(ctx.has_context_node());
1109 free_test_node(node);
1110 }
1111 }
1112
1113 #[test]
1116 fn test_reset() {
1117 let mut ctx = XPathContext::new(std::ptr::null_mut());
1118
1119 ctx.set_error("test error");
1121 ctx.proximity_position = 5;
1122 ctx.context_position = 5;
1123 ctx.context_size = 10;
1124 ctx.recursion_depth = 3;
1125 {
1126 let sentinel = std::ptr::dangling_mut::<_xmlNode>();
1127 ctx.context_list = vec![sentinel];
1128 }
1129
1130 ctx.register_variable("x", XPathValue::Number(42.0));
1132 ctx.register_namespace("p", "http://example.com/ns");
1133 fn dummy(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1134 Ok(XPathValue::Boolean(true))
1135 }
1136 ctx.register_function("f", dummy);
1137
1138 ctx.reset();
1139
1140 assert!(ctx.context_node.is_null());
1142 assert_eq!(ctx.context_position, 1);
1143 assert_eq!(ctx.context_size, 1);
1144 assert_eq!(ctx.proximity_position, 1);
1145 assert!(ctx.error.is_none());
1146 assert!(ctx.context_list.is_empty());
1147 assert_eq!(ctx.recursion_depth, 0);
1148
1149 assert!(ctx.resolve_variable("x").is_some());
1151 assert!(ctx.resolve_namespace("p").is_some());
1152 assert!(ctx.lookup_function("f").is_some());
1153 }
1154
1155 #[test]
1158 fn test_callback_fields_default_to_none() {
1159 let ctx = XPathContext::new(std::ptr::null_mut());
1160 assert!(ctx.var_lookup_func.is_none());
1161 assert!(ctx.var_lookup_data.is_null());
1162 assert!(ctx.func_lookup_func.is_none());
1163 assert!(ctx.func_lookup_data.is_null());
1164 }
1165
1166 #[test]
1167 fn test_set_callback_fields() {
1168 let mut ctx = XPathContext::new(std::ptr::null_mut());
1169
1170 unsafe extern "C" fn dummy_var_lookup(
1178 _data: *mut c_void,
1179 _ns: *const xmlChar,
1180 _name: *const xmlChar,
1181 ) -> *mut _xmlXPathObject {
1182 std::ptr::null_mut()
1183 }
1184
1185 unsafe extern "C" fn dummy_func_lookup(
1193 _data: *mut c_void,
1194 _ns: *const xmlChar,
1195 _name: *const xmlChar,
1196 ) -> *mut c_void {
1197 std::ptr::null_mut()
1198 }
1199
1200 let data_ptr = &mut 42u32 as *mut u32 as *mut c_void;
1201
1202 ctx.var_lookup_func = Some(dummy_var_lookup);
1203 ctx.var_lookup_data = data_ptr;
1204 ctx.func_lookup_func = Some(dummy_func_lookup);
1205 ctx.func_lookup_data = data_ptr;
1206
1207 assert!(ctx.var_lookup_func.is_some());
1208 assert!(!ctx.var_lookup_data.is_null());
1209 assert!(ctx.func_lookup_func.is_some());
1210 assert!(!ctx.func_lookup_data.is_null());
1211 }
1212
1213 #[test]
1216 fn test_context_clone() {
1217 let mut ctx = XPathContext::new(std::ptr::null_mut());
1218 ctx.register_variable("x", XPathValue::Number(10.0));
1219 ctx.register_namespace("ns", "http://example.com/ns");
1220 ctx.set_error("clone test");
1221
1222 let cloned = ctx.clone();
1223 assert_eq!(cloned.document, ctx.document);
1224 assert_eq!(cloned.context_node, ctx.context_node);
1225 assert_eq!(cloned.context_position, ctx.context_position);
1226 assert_eq!(cloned.context_size, ctx.context_size);
1227 assert_eq!(cloned.error, ctx.error);
1228
1229 let var = cloned.resolve_variable("x");
1231 assert!(var.is_some());
1232 assert!((var.unwrap().as_number() - 10.0).abs() < 1e-10);
1233
1234 let ns = cloned.resolve_namespace("ns");
1235 assert!(ns.is_some());
1236 assert_eq!(ns.unwrap(), "http://example.com/ns");
1237 }
1238
1239 #[test]
1242 fn test_context_debug_format() {
1243 let ctx = XPathContext::new(std::ptr::null_mut());
1244 let debug_str = format!("{:?}", ctx);
1245 assert!(debug_str.contains("context_position"));
1246 assert!(debug_str.contains("context_size"));
1247 assert!(debug_str.contains("recursion_depth"));
1248 }
1249
1250 #[test]
1253 fn test_context_size_zero() {
1254 let mut ctx = XPathContext::new(std::ptr::null_mut());
1255 ctx.set_context_list(vec![]);
1256 assert_eq!(ctx.last(), 0);
1257 assert_eq!(ctx.position(), 1);
1258 }
1259
1260 #[test]
1261 fn test_multiple_advancements() {
1262 let mut ctx = XPathContext::new(std::ptr::null_mut());
1263 for i in 1..=10 {
1264 assert_eq!(ctx.position(), i);
1265 ctx.advance_position();
1266 }
1267 assert_eq!(ctx.position(), 11);
1268 }
1269
1270 #[test]
1271 fn test_register_multiple_variables() {
1272 let mut ctx = XPathContext::new(std::ptr::null_mut());
1273 ctx.register_variable("a", XPathValue::Number(1.0));
1274 ctx.register_variable("b", XPathValue::String("two".to_string()));
1275 ctx.register_variable("c", XPathValue::Boolean(true));
1276
1277 assert_eq!(ctx.variables.len(), 3);
1278 assert_eq!(ctx.resolve_variable("a").unwrap().as_number(), 1.0);
1279 assert_eq!(ctx.resolve_variable("b").unwrap().as_string(), "two");
1280 assert!(ctx.resolve_variable("c").unwrap().as_boolean());
1281 }
1282
1283 #[test]
1284 fn test_register_multiple_namespaces() {
1285 let mut ctx = XPathContext::new(std::ptr::null_mut());
1286 ctx.register_namespace("a", "http://example.com/a");
1287 ctx.register_namespace("b", "http://example.com/b");
1288 ctx.register_namespace("c", "http://example.com/c");
1289
1290 assert_eq!(ctx.namespaces.len(), 3);
1291 assert_eq!(ctx.resolve_namespace("a").unwrap(), "http://example.com/a");
1292 assert_eq!(ctx.resolve_namespace("b").unwrap(), "http://example.com/b");
1293 assert_eq!(ctx.resolve_namespace("c").unwrap(), "http://example.com/c");
1294 }
1295
1296 #[test]
1297 fn test_register_multiple_functions() {
1298 fn f1(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1299 Ok(XPathValue::Number(1.0))
1300 }
1301 fn f2(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1302 Ok(XPathValue::Number(2.0))
1303 }
1304 fn f3(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1305 Ok(XPathValue::Number(3.0))
1306 }
1307
1308 let mut ctx = XPathContext::new(std::ptr::null_mut());
1309 ctx.register_function("f1", f1);
1310 ctx.register_function("f2", f2);
1311 ctx.register_function("f3", f3);
1312
1313 assert_eq!(ctx.functions.len(), 3);
1314 }
1315}