1use crate::abi::structs::{_xmlDoc, _xmlNode, _xmlXPathObject};
72use crate::abi::types::xmlChar;
73use crate::xml::xpath::types::XPathValue;
74use std::collections::HashMap;
75use std::os::raw::c_void;
76
77pub type XPathFunction = fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String>;
87
88pub type BoxedXPathFunction =
94 Box<dyn Fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String> + Send + Sync>;
95
96pub type FunctionLookupFn =
100 Box<dyn Fn(&XPathContext, &str) -> Option<BoxedXPathFunction> + Send + Sync>;
101
102pub type VarLookupFunc =
115 unsafe extern "C" fn(*mut c_void, *const xmlChar, *const xmlChar) -> *mut _xmlXPathObject;
116
117pub type FuncLookupFunc =
130 unsafe extern "C" fn(*mut c_void, *const xmlChar, *const xmlChar) -> *mut c_void;
131
132pub struct XPathContext {
154 pub document: *mut _xmlDoc,
156
157 pub context_node: *mut _xmlNode,
159
160 pub context_position: i32,
162
163 pub context_size: i32,
165
166 pub variables: HashMap<String, XPathValue>,
168
169 pub namespaces: HashMap<String, String>,
171
172 pub functions: HashMap<String, BoxedXPathFunction>,
174
175 pub function_lookup: Option<FunctionLookupFn>,
178
179 pub error: Option<String>,
181
182 pub proximity_position: i32,
184
185 pub context_list: Vec<*mut _xmlNode>,
187
188 pub recursion_depth: u32,
190
191 pub var_lookup_func: Option<VarLookupFunc>,
193
194 pub var_lookup_data: *mut c_void,
196
197 pub func_lookup_func: Option<FuncLookupFunc>,
199
200 pub func_lookup_data: *mut c_void,
202}
203
204impl std::fmt::Debug for XPathContext {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 let names: Vec<&String> = self.functions.keys().collect();
209 f.debug_struct("XPathContext")
210 .field("document", &self.document)
211 .field("context_node", &self.context_node)
212 .field("context_position", &self.context_position)
213 .field("context_size", &self.context_size)
214 .field("variables", &self.variables)
215 .field("namespaces", &self.namespaces)
216 .field("functions", &names)
217 .field("error", &self.error)
218 .field("recursion_depth", &self.recursion_depth)
219 .finish()
220 }
221}
222
223impl Clone for XPathContext {
224 fn clone(&self) -> Self {
225 let mut cloned = XPathContext::new(self.document);
228 cloned.context_node = self.context_node;
229 cloned.context_position = self.context_position;
230 cloned.context_size = self.context_size;
231 cloned.variables = self.variables.clone();
232 cloned.namespaces = self.namespaces.clone();
233 cloned.error = self.error.clone();
234 cloned.proximity_position = self.proximity_position;
235 cloned.context_list = self.context_list.clone();
236 cloned.recursion_depth = self.recursion_depth;
237 cloned.var_lookup_func = self.var_lookup_func;
238 cloned.var_lookup_data = self.var_lookup_data;
239 cloned.func_lookup_func = self.func_lookup_func;
240 cloned.func_lookup_data = self.func_lookup_data;
241 cloned
242 }
243}
244
245impl XPathContext {
246 pub fn new(doc: *mut _xmlDoc) -> Self {
260 Self {
261 document: doc,
262 context_node: std::ptr::null_mut(),
263 context_position: 1,
264 context_size: 1,
265 variables: HashMap::new(),
266 namespaces: HashMap::new(),
267 functions: HashMap::new(),
268 function_lookup: None,
269 error: None,
270 proximity_position: 1,
271 context_list: Vec::new(),
272 recursion_depth: 0,
273 var_lookup_func: None,
274 var_lookup_data: std::ptr::null_mut(),
275 func_lookup_func: None,
276 func_lookup_data: std::ptr::null_mut(),
277 }
278 }
279
280 pub fn set_context_node(&mut self, node: *mut _xmlNode) {
289 self.context_node = node;
290 if node.is_null() {
291 self.context_list.clear();
292 self.context_position = 1;
293 self.context_size = 1;
294 self.proximity_position = 1;
295 } else {
296 self.context_list = vec![node];
297 self.context_position = 1;
298 self.context_size = 1;
299 self.proximity_position = 1;
300 }
301 }
302
303 pub fn set_context_list(&mut self, nodes: Vec<*mut _xmlNode>) {
311 self.context_size = nodes.len() as i32;
312 self.context_list = nodes;
313 self.context_position = 1;
314 self.proximity_position = 1;
315 }
316
317 pub fn resolve_variable(&self, name: &str) -> Option<XPathValue> {
333 if let Some(value) = self.variables.get(name) {
335 return Some(value.clone());
336 }
337
338 if let Some(lookup) = self.var_lookup_func {
340 let c_name: Vec<xmlChar> = name.bytes().collect();
342 let result = unsafe { lookup(self.var_lookup_data, std::ptr::null(), c_name.as_ptr()) };
345 if !result.is_null() {
346 {
351 let _ = result; }
356 }
357 }
358
359 None
360 }
361
362 pub fn resolve_namespace(&self, prefix: &str) -> Option<String> {
370 if let Some(uri) = self.namespaces.get(prefix) {
372 return Some(uri.clone());
373 }
374
375 let mut current = self.context_node;
378 while !current.is_null() {
379 unsafe {
382 let mut ns = (*current).nsDef;
383 while !ns.is_null() {
384 let ns_prefix = (*ns).prefix;
385 let ns_href = (*ns).href;
386
387 let prefix_matches = if ns_prefix.is_null() {
389 prefix.is_empty()
392 } else {
393 let mut len = 0;
395 while *ns_prefix.add(len) != 0 {
396 len += 1;
397 }
398 let slice = std::slice::from_raw_parts(ns_prefix, len);
399 slice == prefix.as_bytes()
400 };
401
402 if prefix_matches {
403 let mut len = 0;
405 while *ns_href.add(len) != 0 {
406 len += 1;
407 }
408 let slice = std::slice::from_raw_parts(ns_href, len);
409 return Some(String::from_utf8_lossy(slice).into_owned());
410 }
411
412 ns = (*ns).next;
413 }
414 }
415
416 unsafe {
419 current = (*current).parent;
420 }
421 }
422
423 None
424 }
425
426 pub fn lookup_function(&mut self, name: &str) -> Option<&BoxedXPathFunction> {
433 if self.functions.contains_key(name) {
435 return self.functions.get(name);
436 }
437
438 if let Some(lookup) = &self.function_lookup {
441 if let Some(func) = lookup(self, name) {
442 self.functions.insert(name.to_string(), func);
443 return self.functions.get(name);
444 }
445 }
446
447 if let Some(lookup) = self.func_lookup_func {
449 let c_name: Vec<xmlChar> = name.bytes().collect();
450 let _result =
452 unsafe { lookup(self.func_lookup_data, std::ptr::null(), c_name.as_ptr()) };
453 }
457
458 None
459 }
460
461 pub fn register_function<F>(&mut self, name: &str, func: F)
468 where
469 F: Fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String>
470 + Send
471 + Sync
472 + 'static,
473 {
474 self.functions.insert(name.to_string(), Box::new(func));
475 }
476
477 pub fn register_variable(&mut self, name: &str, value: XPathValue) {
483 self.variables.insert(name.to_string(), value);
484 }
485
486 pub fn unregister_variable(&mut self, name: &str) {
491 self.variables.remove(name);
492 }
493
494 pub fn register_namespace(&mut self, prefix: &str, uri: &str) {
499 self.namespaces.insert(prefix.to_string(), uri.to_string());
500 }
501
502 pub fn set_error(&mut self, msg: &str) {
506 self.error = Some(msg.to_string());
507 }
508
509 pub fn clear_error(&mut self) {
511 self.error = None;
512 }
513
514 pub fn push_recursion(&mut self) -> Result<(), String> {
522 const MAX_RECURSION_DEPTH: u32 = 1000;
523 if self.recursion_depth >= MAX_RECURSION_DEPTH {
524 return Err(
525 "XPath evaluation recursion depth exceeded (infinite recursion?)".to_string(),
526 );
527 }
528 self.recursion_depth += 1;
529 Ok(())
530 }
531
532 pub fn pop_recursion(&mut self) {
542 assert!(
543 self.recursion_depth > 0,
544 "unbalanced pop_recursion: recursion_depth is already 0"
545 );
546 self.recursion_depth -= 1;
547 }
548
549 pub const fn has_context_node(&self) -> bool {
551 !self.context_node.is_null()
552 }
553
554 pub fn reset(&mut self) {
559 self.context_node = std::ptr::null_mut();
560 self.context_position = 1;
561 self.context_size = 1;
562 self.error = None;
563 self.proximity_position = 1;
564 self.context_list.clear();
565 self.recursion_depth = 0;
566 }
567
568 pub const fn position(&self) -> i32 {
572 self.proximity_position
573 }
574
575 pub const fn last(&self) -> i32 {
579 self.context_size
580 }
581
582 pub const fn advance_position(&mut self) {
587 self.proximity_position += 1;
588 self.context_position = self.proximity_position;
589 }
590
591 pub const fn reset_position(&mut self) {
593 self.proximity_position = 1;
594 self.context_position = 1;
595 }
596}
597
598impl Default for XPathContext {
599 fn default() -> Self {
604 Self::new(std::ptr::null_mut())
605 }
606}
607
608#[cfg(test)]
613mod tests {
614 use super::*;
615
616 use crate::xml::xpath::types::NodeSet;
617
618 unsafe fn create_test_doc() -> *mut _xmlDoc {
624 let layout = std::alloc::Layout::new::<_xmlDoc>();
626 let ptr = std::alloc::alloc_zeroed(layout) as *mut _xmlDoc;
627 assert!(!ptr.is_null(), "failed to allocate test document");
628 ptr
629 }
630
631 unsafe fn create_test_node() -> *mut _xmlNode {
635 let layout = std::alloc::Layout::new::<_xmlNode>();
636 let ptr = std::alloc::alloc_zeroed(layout) as *mut _xmlNode;
637 assert!(!ptr.is_null(), "failed to allocate test node");
638 ptr
639 }
640
641 unsafe fn free_test_doc(doc: *mut _xmlDoc) {
643 if !doc.is_null() {
644 let layout = std::alloc::Layout::new::<_xmlDoc>();
645 std::alloc::dealloc(doc as *mut u8, layout);
646 }
647 }
648
649 unsafe fn free_test_node(node: *mut _xmlNode) {
651 if !node.is_null() {
652 let layout = std::alloc::Layout::new::<_xmlNode>();
653 std::alloc::dealloc(node as *mut u8, layout);
654 }
655 }
656
657 #[test]
660 fn test_new_context() {
661 let ctx = XPathContext::new(std::ptr::null_mut());
662 assert!(ctx.document.is_null());
663 assert!(ctx.context_node.is_null());
664 assert_eq!(ctx.context_position, 1);
665 assert_eq!(ctx.context_size, 1);
666 assert!(ctx.variables.is_empty());
667 assert!(ctx.namespaces.is_empty());
668 assert!(ctx.functions.is_empty());
669 assert!(ctx.error.is_none());
670 assert_eq!(ctx.proximity_position, 1);
671 assert!(ctx.context_list.is_empty());
672 assert_eq!(ctx.recursion_depth, 0);
673 assert!(ctx.var_lookup_func.is_none());
674 assert!(ctx.var_lookup_data.is_null());
675 assert!(ctx.func_lookup_func.is_none());
676 assert!(ctx.func_lookup_data.is_null());
677 }
678
679 #[test]
680 fn test_default_context() {
681 let ctx = XPathContext::default();
682 assert!(ctx.document.is_null());
683 assert_eq!(ctx.context_position, 1);
684 }
685
686 #[test]
687 fn test_new_with_doc() {
688 unsafe {
689 let doc = create_test_doc();
690 let ctx = XPathContext::new(doc);
691 assert_eq!(ctx.document, doc);
692 free_test_doc(doc);
693 }
694 }
695
696 #[test]
699 fn test_set_context_node_non_null() {
700 unsafe {
701 let node = create_test_node();
702 let mut ctx = XPathContext::new(std::ptr::null_mut());
703 ctx.set_context_node(node);
704
705 assert_eq!(ctx.context_node, node);
706 assert_eq!(ctx.context_position, 1);
707 assert_eq!(ctx.context_size, 1);
708 assert_eq!(ctx.proximity_position, 1);
709 assert_eq!(ctx.context_list.len(), 1);
710 assert_eq!(ctx.context_list[0], node);
711
712 free_test_node(node);
713 }
714 }
715
716 #[test]
717 fn test_set_context_node_null() {
718 let mut ctx = XPathContext::new(std::ptr::null_mut());
719 let sentinel = std::ptr::dangling_mut::<_xmlNode>();
722 ctx.context_list = vec![sentinel];
723 ctx.context_position = 5;
724 ctx.context_size = 5;
725 ctx.proximity_position = 5;
726
727 ctx.set_context_node(std::ptr::null_mut());
729 assert!(ctx.context_node.is_null());
730 assert!(ctx.context_list.is_empty());
731 assert_eq!(ctx.context_position, 1);
732 assert_eq!(ctx.context_size, 1);
733 assert_eq!(ctx.proximity_position, 1);
734 }
735
736 #[test]
739 fn test_set_context_list() {
740 unsafe {
741 let node1 = create_test_node();
742 let node2 = create_test_node();
743 let nodes = vec![node1, node2];
744
745 let mut ctx = XPathContext::new(std::ptr::null_mut());
746 ctx.set_context_list(nodes.clone());
747
748 assert_eq!(ctx.context_list.len(), 2);
749 assert_eq!(ctx.context_size, 2);
750 assert_eq!(ctx.context_position, 1);
751 assert_eq!(ctx.proximity_position, 1);
752
753 free_test_node(node1);
754 free_test_node(node2);
755 }
756 }
757
758 #[test]
759 fn test_set_context_list_empty() {
760 let mut ctx = XPathContext::new(std::ptr::null_mut());
761 ctx.set_context_list(vec![]);
762
763 assert!(ctx.context_list.is_empty());
764 assert_eq!(ctx.context_size, 0);
765 assert_eq!(ctx.context_position, 1);
766 }
767
768 #[test]
771 fn test_register_and_resolve_variable() {
772 let mut ctx = XPathContext::new(std::ptr::null_mut());
773 ctx.register_variable("foo", XPathValue::String("bar".to_string()));
774
775 let result = ctx.resolve_variable("foo");
776 assert!(result.is_some());
777 assert_eq!(result.unwrap().as_string(), "bar");
778 }
779
780 #[test]
781 fn test_resolve_unknown_variable() {
782 let ctx = XPathContext::new(std::ptr::null_mut());
783 assert!(ctx.resolve_variable("nonexistent").is_none());
784 }
785 #[allow(clippy::approx_constant)]
786 #[test]
787 fn test_register_variable_number() {
788 let mut ctx = XPathContext::new(std::ptr::null_mut());
789 ctx.register_variable("pi", XPathValue::Number(3.14159));
790
791 let result = ctx.resolve_variable("pi");
792 assert!(result.is_some());
793 let val = result.unwrap();
794 assert!((val.as_number() - 3.14159).abs() < 1e-10);
795 }
796
797 #[test]
798 fn test_register_variable_boolean() {
799 let mut ctx = XPathContext::new(std::ptr::null_mut());
800 ctx.register_variable("flag", XPathValue::Boolean(true));
801
802 let result = ctx.resolve_variable("flag");
803 assert!(result.is_some());
804 assert!(result.unwrap().as_boolean());
805 }
806
807 #[test]
808 fn test_register_variable_nodeset() {
809 let mut ctx = XPathContext::new(std::ptr::null_mut());
810 let ns = NodeSet::new();
811 ctx.register_variable("nodes", XPathValue::NodeSet(ns));
812
813 let result = ctx.resolve_variable("nodes");
814 assert!(result.is_some());
815 assert!(matches!(result.unwrap(), XPathValue::NodeSet(_)));
816 }
817
818 #[test]
819 fn test_variable_overwrite() {
820 let mut ctx = XPathContext::new(std::ptr::null_mut());
821 ctx.register_variable("x", XPathValue::Number(1.0));
822 ctx.register_variable("x", XPathValue::Number(2.0));
823
824 let result = ctx.resolve_variable("x");
825 assert!(result.is_some());
826 assert!((result.unwrap().as_number() - 2.0).abs() < 1e-10);
827 }
828
829 #[test]
832 fn test_register_and_resolve_namespace() {
833 let mut ctx = XPathContext::new(std::ptr::null_mut());
834 ctx.register_namespace("xslt", "http://www.w3.org/1999/XSL/Transform");
835
836 let result = ctx.resolve_namespace("xslt");
837 assert!(result.is_some());
838 assert_eq!(result.unwrap(), "http://www.w3.org/1999/XSL/Transform");
839 }
840
841 #[test]
842 fn test_resolve_unknown_namespace() {
843 let ctx = XPathContext::new(std::ptr::null_mut());
844 assert!(ctx.resolve_namespace("unknown").is_none());
846 }
847
848 #[test]
849 fn test_register_default_namespace() {
850 let mut ctx = XPathContext::new(std::ptr::null_mut());
851 ctx.register_namespace("", "http://example.com/default");
852
853 let result = ctx.resolve_namespace("");
854 assert!(result.is_some());
855 assert_eq!(result.unwrap(), "http://example.com/default");
856 }
857
858 #[test]
859 fn test_namespace_overwrite() {
860 let mut ctx = XPathContext::new(std::ptr::null_mut());
861 ctx.register_namespace("a", "http://example.com/1");
862 ctx.register_namespace("a", "http://example.com/2");
863
864 let result = ctx.resolve_namespace("a");
865 assert_eq!(result.unwrap(), "http://example.com/2");
866 }
867
868 #[test]
871 fn test_register_and_lookup_function() {
872 fn test_func(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
873 Ok(XPathValue::String("test".to_string()))
874 }
875
876 let mut ctx = XPathContext::new(std::ptr::null_mut());
877 ctx.register_function("test:func", test_func);
878
879 let result = ctx.lookup_function("test:func");
880 assert!(result.is_some());
881 }
882
883 #[test]
884 fn test_lookup_unknown_function() {
885 let mut ctx = XPathContext::new(std::ptr::null_mut());
886 assert!(ctx.lookup_function("nonexistent").is_none());
887 }
888
889 #[test]
890 fn test_function_overwrite() {
891 fn func_a(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
892 Ok(XPathValue::String("a".to_string()))
893 }
894 fn func_b(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
895 Ok(XPathValue::String("b".to_string()))
896 }
897
898 let mut ctx = XPathContext::new(std::ptr::null_mut());
899 ctx.register_function("f", func_a);
900 ctx.register_function("f", func_b);
901
902 let result = ctx.lookup_function("f");
903 assert!(result.is_some());
904
905 if let Some(f) = result {
907 let mut tmp_ctx = XPathContext::new(std::ptr::null_mut());
908 let value = f(&mut tmp_ctx, &[]).unwrap();
909 assert_eq!(value.as_string(), "b");
910 }
911 }
912
913 #[test]
916 fn test_set_and_get_error() {
917 let mut ctx = XPathContext::new(std::ptr::null_mut());
918 assert!(ctx.error.is_none());
919
920 ctx.set_error("something went wrong");
921 assert_eq!(ctx.error.as_deref(), Some("something went wrong"));
922 }
923
924 #[test]
925 fn test_clear_error() {
926 let mut ctx = XPathContext::new(std::ptr::null_mut());
927 ctx.set_error("an error");
928 assert!(ctx.error.is_some());
929
930 ctx.clear_error();
931 assert!(ctx.error.is_none());
932 }
933
934 #[test]
935 fn test_error_overwrite() {
936 let mut ctx = XPathContext::new(std::ptr::null_mut());
937 ctx.set_error("first error");
938 ctx.set_error("second error");
939 assert_eq!(ctx.error.as_deref(), Some("second error"));
940 }
941
942 #[test]
945 fn test_push_pop_recursion() {
946 let mut ctx = XPathContext::new(std::ptr::null_mut());
947 assert_eq!(ctx.recursion_depth, 0);
948
949 assert!(ctx.push_recursion().is_ok());
950 assert_eq!(ctx.recursion_depth, 1);
951
952 ctx.pop_recursion();
953 assert_eq!(ctx.recursion_depth, 0);
954 }
955
956 #[test]
957 fn test_recursion_depth_limit() {
958 let mut ctx = XPathContext::new(std::ptr::null_mut());
959
960 for _ in 0..1000 {
962 assert!(ctx.push_recursion().is_ok());
963 }
964 assert_eq!(ctx.recursion_depth, 1000);
965
966 let result = ctx.push_recursion();
968 assert!(result.is_err());
969 assert!(result.unwrap_err().contains("recursion depth exceeded"));
970
971 for _ in 0..1000 {
973 ctx.pop_recursion();
974 }
975 assert_eq!(ctx.recursion_depth, 0);
976 }
977
978 #[test]
979 #[should_panic(expected = "unbalanced pop_recursion")]
980 fn test_pop_recursion_underflow() {
981 let mut ctx = XPathContext::new(std::ptr::null_mut());
982 ctx.pop_recursion(); }
984
985 #[test]
986 fn test_recursion_nesting() {
987 let mut ctx = XPathContext::new(std::ptr::null_mut());
988
989 assert!(ctx.push_recursion().is_ok());
991 assert!(ctx.push_recursion().is_ok());
992 assert!(ctx.push_recursion().is_ok());
993 assert_eq!(ctx.recursion_depth, 3);
994
995 ctx.pop_recursion();
996 assert_eq!(ctx.recursion_depth, 2);
997
998 ctx.pop_recursion();
999 assert_eq!(ctx.recursion_depth, 1);
1000
1001 ctx.pop_recursion();
1002 assert_eq!(ctx.recursion_depth, 0);
1003 }
1004
1005 #[test]
1008 fn test_position_and_last() {
1009 let ctx = XPathContext::new(std::ptr::null_mut());
1010 assert_eq!(ctx.position(), 1);
1011 assert_eq!(ctx.last(), 1);
1012 }
1013
1014 #[test]
1015 fn test_advance_position() {
1016 let mut ctx = XPathContext::new(std::ptr::null_mut());
1017 ctx.advance_position();
1018 assert_eq!(ctx.position(), 2);
1019 assert_eq!(ctx.proximity_position, 2);
1020 assert_eq!(ctx.context_position, 2);
1021 }
1022
1023 #[test]
1024 fn test_reset_position() {
1025 let mut ctx = XPathContext::new(std::ptr::null_mut());
1026 ctx.advance_position();
1027 ctx.advance_position();
1028 ctx.advance_position();
1029 assert_eq!(ctx.position(), 4);
1030
1031 ctx.reset_position();
1032 assert_eq!(ctx.position(), 1);
1033 assert_eq!(ctx.context_position, 1);
1034 }
1035
1036 #[test]
1037 fn test_position_with_context_list() {
1038 unsafe {
1039 let node1 = create_test_node();
1040 let node2 = create_test_node();
1041 let node3 = create_test_node();
1042 let nodes = vec![node1, node2, node3];
1043
1044 let mut ctx = XPathContext::new(std::ptr::null_mut());
1045 ctx.set_context_list(nodes);
1046
1047 assert_eq!(ctx.last(), 3);
1048 assert_eq!(ctx.position(), 1);
1049
1050 ctx.advance_position();
1051 assert_eq!(ctx.position(), 2);
1052
1053 ctx.advance_position();
1054 assert_eq!(ctx.position(), 3);
1055
1056 free_test_node(node1);
1057 free_test_node(node2);
1058 free_test_node(node3);
1059 }
1060 }
1061
1062 #[test]
1065 fn test_has_context_node() {
1066 let mut ctx = XPathContext::new(std::ptr::null_mut());
1067 assert!(!ctx.has_context_node());
1068
1069 unsafe {
1070 let node = create_test_node();
1071 ctx.set_context_node(node);
1072 assert!(ctx.has_context_node());
1073 free_test_node(node);
1074 }
1075 }
1076
1077 #[test]
1080 fn test_reset() {
1081 let mut ctx = XPathContext::new(std::ptr::null_mut());
1082
1083 ctx.set_error("test error");
1085 ctx.proximity_position = 5;
1086 ctx.context_position = 5;
1087 ctx.context_size = 10;
1088 ctx.recursion_depth = 3;
1089 {
1090 let sentinel = std::ptr::dangling_mut::<_xmlNode>();
1091 ctx.context_list = vec![sentinel];
1092 }
1093
1094 ctx.register_variable("x", XPathValue::Number(42.0));
1096 ctx.register_namespace("p", "http://example.com/ns");
1097 fn dummy(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1098 Ok(XPathValue::Boolean(true))
1099 }
1100 ctx.register_function("f", dummy);
1101
1102 ctx.reset();
1103
1104 assert!(ctx.context_node.is_null());
1106 assert_eq!(ctx.context_position, 1);
1107 assert_eq!(ctx.context_size, 1);
1108 assert_eq!(ctx.proximity_position, 1);
1109 assert!(ctx.error.is_none());
1110 assert!(ctx.context_list.is_empty());
1111 assert_eq!(ctx.recursion_depth, 0);
1112
1113 assert!(ctx.resolve_variable("x").is_some());
1115 assert!(ctx.resolve_namespace("p").is_some());
1116 assert!(ctx.lookup_function("f").is_some());
1117 }
1118
1119 #[test]
1122 fn test_callback_fields_default_to_none() {
1123 let ctx = XPathContext::new(std::ptr::null_mut());
1124 assert!(ctx.var_lookup_func.is_none());
1125 assert!(ctx.var_lookup_data.is_null());
1126 assert!(ctx.func_lookup_func.is_none());
1127 assert!(ctx.func_lookup_data.is_null());
1128 }
1129
1130 #[test]
1131 fn test_set_callback_fields() {
1132 let mut ctx = XPathContext::new(std::ptr::null_mut());
1133
1134 unsafe extern "C" fn dummy_var_lookup(
1135 _data: *mut c_void,
1136 _ns: *const xmlChar,
1137 _name: *const xmlChar,
1138 ) -> *mut _xmlXPathObject {
1139 std::ptr::null_mut()
1140 }
1141
1142 unsafe extern "C" fn dummy_func_lookup(
1143 _data: *mut c_void,
1144 _ns: *const xmlChar,
1145 _name: *const xmlChar,
1146 ) -> *mut c_void {
1147 std::ptr::null_mut()
1148 }
1149
1150 let data_ptr = &mut 42u32 as *mut u32 as *mut c_void;
1151
1152 ctx.var_lookup_func = Some(dummy_var_lookup);
1153 ctx.var_lookup_data = data_ptr;
1154 ctx.func_lookup_func = Some(dummy_func_lookup);
1155 ctx.func_lookup_data = data_ptr;
1156
1157 assert!(ctx.var_lookup_func.is_some());
1158 assert!(!ctx.var_lookup_data.is_null());
1159 assert!(ctx.func_lookup_func.is_some());
1160 assert!(!ctx.func_lookup_data.is_null());
1161 }
1162
1163 #[test]
1166 fn test_context_clone() {
1167 let mut ctx = XPathContext::new(std::ptr::null_mut());
1168 ctx.register_variable("x", XPathValue::Number(10.0));
1169 ctx.register_namespace("ns", "http://example.com/ns");
1170 ctx.set_error("clone test");
1171
1172 let cloned = ctx.clone();
1173 assert_eq!(cloned.document, ctx.document);
1174 assert_eq!(cloned.context_node, ctx.context_node);
1175 assert_eq!(cloned.context_position, ctx.context_position);
1176 assert_eq!(cloned.context_size, ctx.context_size);
1177 assert_eq!(cloned.error, ctx.error);
1178
1179 let var = cloned.resolve_variable("x");
1181 assert!(var.is_some());
1182 assert!((var.unwrap().as_number() - 10.0).abs() < 1e-10);
1183
1184 let ns = cloned.resolve_namespace("ns");
1185 assert!(ns.is_some());
1186 assert_eq!(ns.unwrap(), "http://example.com/ns");
1187 }
1188
1189 #[test]
1192 fn test_context_debug_format() {
1193 let ctx = XPathContext::new(std::ptr::null_mut());
1194 let debug_str = format!("{:?}", ctx);
1195 assert!(debug_str.contains("context_position"));
1196 assert!(debug_str.contains("context_size"));
1197 assert!(debug_str.contains("recursion_depth"));
1198 }
1199
1200 #[test]
1203 fn test_context_size_zero() {
1204 let mut ctx = XPathContext::new(std::ptr::null_mut());
1205 ctx.set_context_list(vec![]);
1206 assert_eq!(ctx.last(), 0);
1207 assert_eq!(ctx.position(), 1);
1208 }
1209
1210 #[test]
1211 fn test_multiple_advancements() {
1212 let mut ctx = XPathContext::new(std::ptr::null_mut());
1213 for i in 1..=10 {
1214 assert_eq!(ctx.position(), i);
1215 ctx.advance_position();
1216 }
1217 assert_eq!(ctx.position(), 11);
1218 }
1219
1220 #[test]
1221 fn test_register_multiple_variables() {
1222 let mut ctx = XPathContext::new(std::ptr::null_mut());
1223 ctx.register_variable("a", XPathValue::Number(1.0));
1224 ctx.register_variable("b", XPathValue::String("two".to_string()));
1225 ctx.register_variable("c", XPathValue::Boolean(true));
1226
1227 assert_eq!(ctx.variables.len(), 3);
1228 assert_eq!(ctx.resolve_variable("a").unwrap().as_number(), 1.0);
1229 assert_eq!(ctx.resolve_variable("b").unwrap().as_string(), "two");
1230 assert!(ctx.resolve_variable("c").unwrap().as_boolean());
1231 }
1232
1233 #[test]
1234 fn test_register_multiple_namespaces() {
1235 let mut ctx = XPathContext::new(std::ptr::null_mut());
1236 ctx.register_namespace("a", "http://example.com/a");
1237 ctx.register_namespace("b", "http://example.com/b");
1238 ctx.register_namespace("c", "http://example.com/c");
1239
1240 assert_eq!(ctx.namespaces.len(), 3);
1241 assert_eq!(ctx.resolve_namespace("a").unwrap(), "http://example.com/a");
1242 assert_eq!(ctx.resolve_namespace("b").unwrap(), "http://example.com/b");
1243 assert_eq!(ctx.resolve_namespace("c").unwrap(), "http://example.com/c");
1244 }
1245
1246 #[test]
1247 fn test_register_multiple_functions() {
1248 fn f1(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1249 Ok(XPathValue::Number(1.0))
1250 }
1251 fn f2(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1252 Ok(XPathValue::Number(2.0))
1253 }
1254 fn f3(_: &mut XPathContext, _: &[XPathValue]) -> Result<XPathValue, String> {
1255 Ok(XPathValue::Number(3.0))
1256 }
1257
1258 let mut ctx = XPathContext::new(std::ptr::null_mut());
1259 ctx.register_function("f1", f1);
1260 ctx.register_function("f2", f2);
1261 ctx.register_function("f3", f3);
1262
1263 assert_eq!(ctx.functions.len(), 3);
1264 }
1265}