1use std::sync::Arc;
29
30use super::signature::{FunctionArity, FunctionSignature};
31use super::{eval_function, FunctionId, XPathValue, FUNCTION_REGISTRY};
32use crate::types::sequence::SequenceType;
33use crate::xpath::context::DynamicContext;
34use crate::xpath::error::XPathError;
35use crate::xpath::DomNavigator;
36
37const CUSTOM_HANDLE_BASE: u32 = 0x1000_0000;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub struct FunctionHandle(pub(crate) u32);
51
52impl FunctionHandle {
53 #[inline]
55 pub fn is_builtin(&self) -> bool {
56 self.0 < CUSTOM_HANDLE_BASE
57 }
58
59 #[inline]
61 pub fn is_custom(&self) -> bool {
62 self.0 >= CUSTOM_HANDLE_BASE
63 }
64
65 #[inline]
67 pub(crate) fn custom_index(&self) -> Option<usize> {
68 if self.is_custom() {
69 Some((self.0 - CUSTOM_HANDLE_BASE) as usize)
70 } else {
71 None
72 }
73 }
74}
75
76impl From<FunctionId> for FunctionHandle {
77 fn from(id: FunctionId) -> Self {
78 FunctionHandle(id as u32)
79 }
80}
81
82#[derive(Debug, Clone)]
91pub struct DynamicFunctionSignature {
92 pub namespace: Arc<str>,
94 pub local_name: Arc<str>,
96 pub arity: FunctionArity,
98 pub param_types: Vec<SequenceType>,
100 pub return_type: SequenceType,
102}
103
104impl DynamicFunctionSignature {
105 pub fn new(
107 namespace: impl Into<Arc<str>>,
108 local_name: impl Into<Arc<str>>,
109 param_types: Vec<SequenceType>,
110 return_type: SequenceType,
111 ) -> Self {
112 let arity = FunctionArity::Exact(param_types.len());
113 Self {
114 namespace: namespace.into(),
115 local_name: local_name.into(),
116 arity,
117 param_types,
118 return_type,
119 }
120 }
121
122 pub fn variadic(
124 namespace: impl Into<Arc<str>>,
125 local_name: impl Into<Arc<str>>,
126 min_args: usize,
127 param_types: Vec<SequenceType>,
128 return_type: SequenceType,
129 ) -> Self {
130 Self {
131 namespace: namespace.into(),
132 local_name: local_name.into(),
133 arity: FunctionArity::Variadic(min_args),
134 param_types,
135 return_type,
136 }
137 }
138
139 pub fn range(
141 namespace: impl Into<Arc<str>>,
142 local_name: impl Into<Arc<str>>,
143 min_args: usize,
144 max_args: usize,
145 param_types: Vec<SequenceType>,
146 return_type: SequenceType,
147 ) -> Self {
148 Self {
149 namespace: namespace.into(),
150 local_name: local_name.into(),
151 arity: FunctionArity::Range(min_args, max_args),
152 param_types,
153 return_type,
154 }
155 }
156
157 pub fn matches_arity(&self, count: usize) -> bool {
159 self.arity.matches(count)
160 }
161}
162
163impl From<&FunctionSignature> for DynamicFunctionSignature {
164 fn from(sig: &FunctionSignature) -> Self {
165 Self {
166 namespace: Arc::from(sig.namespace),
167 local_name: Arc::from(sig.local_name),
168 arity: sig.arity,
169 param_types: sig.param_types.clone(),
170 return_type: sig.return_type.clone(),
171 }
172 }
173}
174
175pub trait FunctionCatalog: std::fmt::Debug {
184 fn lookup(&self, namespace: &str, local_name: &str, arity: usize) -> Option<FunctionHandle>;
188
189 fn get_signature(&self, handle: FunctionHandle) -> Option<DynamicFunctionSignature>;
193}
194
195pub trait FunctionEvaluator<N: DomNavigator> {
203 fn eval(
213 &self,
214 handle: FunctionHandle,
215 ctx: &mut DynamicContext<'_, N>,
216 args: Vec<XPathValue<N>>,
217 ) -> Result<XPathValue<N>, XPathError>;
218}
219
220#[derive(Debug, Clone, Copy, Default)]
229pub struct BuiltinCatalog;
230
231impl FunctionCatalog for BuiltinCatalog {
232 fn lookup(&self, namespace: &str, local_name: &str, arity: usize) -> Option<FunctionHandle> {
233 FUNCTION_REGISTRY
234 .lookup(namespace, local_name, arity)
235 .map(|entry| FunctionHandle::from(entry.id))
236 }
237
238 fn get_signature(&self, handle: FunctionHandle) -> Option<DynamicFunctionSignature> {
239 if !handle.is_builtin() {
240 return None;
241 }
242 FUNCTION_REGISTRY
244 .by_id(handle_to_function_id(handle).ok()?)
245 .map(|entry| DynamicFunctionSignature::from(&entry.signature))
246 }
247}
248
249#[derive(Debug, Clone, Copy, Default)]
258pub struct BuiltinEvaluator;
259
260impl<N: DomNavigator> FunctionEvaluator<N> for BuiltinEvaluator {
261 fn eval(
262 &self,
263 handle: FunctionHandle,
264 ctx: &mut DynamicContext<'_, N>,
265 args: Vec<XPathValue<N>>,
266 ) -> Result<XPathValue<N>, XPathError> {
267 let id = handle_to_function_id(handle)?;
268 eval_function(id, ctx, args)
269 }
270}
271
272pub(crate) fn handle_to_function_id(handle: FunctionHandle) -> Result<FunctionId, XPathError> {
280 if !handle.is_builtin() {
281 return Err(XPathError::Internal(format!(
282 "Cannot convert custom handle {:?} to FunctionId",
283 handle
284 )));
285 }
286
287 let value = handle.0 as u16;
290
291 let entry = FUNCTION_REGISTRY
294 .by_id_value(value)
295 .ok_or_else(|| XPathError::Internal(format!("Invalid function handle: {}", value)))?;
296
297 Ok(entry.id)
298}
299
300use std::collections::HashMap;
305
306pub type CustomFn<N> = Arc<
311 dyn Fn(&mut DynamicContext<'_, N>, Vec<XPathValue<N>>) -> Result<XPathValue<N>, XPathError>
312 + Send
313 + Sync,
314>;
315
316struct CustomFunctionEntry<N: DomNavigator> {
318 signature: DynamicFunctionSignature,
319 implementation: CustomFn<N>,
320}
321
322pub struct FunctionSet<N: DomNavigator> {
350 custom_functions: Vec<CustomFunctionEntry<N>>,
352 lookup: HashMap<(Arc<str>, Arc<str>, usize), FunctionHandle>,
354 variadic_lookup: HashMap<(Arc<str>, Arc<str>), (FunctionHandle, usize)>,
356}
357
358impl<N: DomNavigator> FunctionSet<N> {
359 pub fn new() -> Self {
363 Self {
364 custom_functions: Vec::new(),
365 lookup: HashMap::new(),
366 variadic_lookup: HashMap::new(),
367 }
368 }
369
370 pub fn with_builtins() -> Self {
376 Self::new()
379 }
380
381 pub fn register<F>(
406 &mut self,
407 signature: DynamicFunctionSignature,
408 implementation: F,
409 ) -> FunctionHandle
410 where
411 F: Fn(&mut DynamicContext<'_, N>, Vec<XPathValue<N>>) -> Result<XPathValue<N>, XPathError>
412 + Send
413 + Sync
414 + 'static,
415 {
416 let index = self.custom_functions.len();
417 let handle = FunctionHandle(CUSTOM_HANDLE_BASE + index as u32);
418
419 let ns = signature.namespace.clone();
421 let local = signature.local_name.clone();
422
423 match signature.arity {
424 FunctionArity::Exact(n) => {
425 self.lookup.insert((ns.clone(), local.clone(), n), handle);
426 }
427 FunctionArity::Range(min, max) => {
428 for arity in min..=max {
429 self.lookup
430 .insert((ns.clone(), local.clone(), arity), handle);
431 }
432 }
433 FunctionArity::Variadic(min) => {
434 self.variadic_lookup
435 .insert((ns.clone(), local.clone()), (handle, min));
436 }
437 }
438
439 self.custom_functions.push(CustomFunctionEntry {
441 signature,
442 implementation: Arc::new(implementation),
443 });
444
445 handle
446 }
447
448 pub fn custom_count(&self) -> usize {
450 self.custom_functions.len()
451 }
452
453 pub fn has_custom_functions(&self) -> bool {
455 !self.custom_functions.is_empty()
456 }
457}
458
459impl<N: DomNavigator> Default for FunctionSet<N> {
460 fn default() -> Self {
461 Self::with_builtins()
462 }
463}
464
465impl<N: DomNavigator> std::fmt::Debug for FunctionSet<N> {
466 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
467 f.debug_struct("FunctionSet")
468 .field("custom_count", &self.custom_functions.len())
469 .field("lookup_count", &self.lookup.len())
470 .finish()
471 }
472}
473
474impl<N: DomNavigator> FunctionCatalog for FunctionSet<N> {
475 fn lookup(&self, namespace: &str, local_name: &str, arity: usize) -> Option<FunctionHandle> {
476 let ns: Arc<str> = Arc::from(namespace);
478 let local: Arc<str> = Arc::from(local_name);
479
480 if let Some(&handle) = self.lookup.get(&(ns.clone(), local.clone(), arity)) {
481 return Some(handle);
482 }
483
484 if let Some(&(handle, min_arity)) = self.variadic_lookup.get(&(ns.clone(), local.clone())) {
486 if arity >= min_arity {
487 return Some(handle);
488 }
489 }
490
491 FUNCTION_REGISTRY
493 .lookup(namespace, local_name, arity)
494 .map(|entry| FunctionHandle::from(entry.id))
495 }
496
497 fn get_signature(&self, handle: FunctionHandle) -> Option<DynamicFunctionSignature> {
498 if let Some(index) = handle.custom_index() {
499 self.custom_functions
501 .get(index)
502 .map(|e| e.signature.clone())
503 } else {
504 FUNCTION_REGISTRY
506 .by_id(handle_to_function_id(handle).ok()?)
507 .map(|entry| DynamicFunctionSignature::from(&entry.signature))
508 }
509 }
510}
511
512impl<N: DomNavigator> FunctionEvaluator<N> for FunctionSet<N> {
513 fn eval(
514 &self,
515 handle: FunctionHandle,
516 ctx: &mut DynamicContext<'_, N>,
517 args: Vec<XPathValue<N>>,
518 ) -> Result<XPathValue<N>, XPathError> {
519 if let Some(index) = handle.custom_index() {
520 let entry = self.custom_functions.get(index).ok_or_else(|| {
522 XPathError::Internal(format!("Invalid custom function handle: {:?}", handle))
523 })?;
524 (entry.implementation)(ctx, args)
525 } else {
526 let id = handle_to_function_id(handle)?;
528 eval_function(id, ctx, args)
529 }
530 }
531}
532
533const XPATH10_FUNCTIONS: &[&str] = &[
539 "last",
540 "position",
541 "count",
542 "id",
543 "name",
544 "local-name",
545 "namespace-uri",
546 "lang",
547 "string",
548 "concat",
549 "starts-with",
550 "contains",
551 "substring-before",
552 "substring-after",
553 "substring",
554 "string-length",
555 "normalize-space",
556 "translate",
557 "boolean",
558 "not",
559 "true",
560 "false",
561 "number",
562 "sum",
563 "floor",
564 "ceiling",
565 "round",
566];
567
568#[derive(Debug, Clone, Copy, Default)]
574pub struct XPath10Catalog;
575
576impl FunctionCatalog for XPath10Catalog {
577 fn lookup(&self, namespace: &str, local_name: &str, arity: usize) -> Option<FunctionHandle> {
578 if namespace.is_empty() {
579 if XPATH10_FUNCTIONS.contains(&local_name) {
581 FUNCTION_REGISTRY
582 .lookup(super::signature::FN_NAMESPACE, local_name, arity)
583 .map(|entry| FunctionHandle::from(entry.id))
584 } else {
585 None
586 }
587 } else {
588 BuiltinCatalog.lookup(namespace, local_name, arity)
590 }
591 }
592
593 fn get_signature(&self, handle: FunctionHandle) -> Option<DynamicFunctionSignature> {
594 BuiltinCatalog.get_signature(handle)
595 }
596}
597
598fn wrap_as_double<N: DomNavigator>(result: XPathValue<N>) -> XPathValue<N> {
606 if let Some(i) = result.as_integer() {
608 return XPathValue::double(i.to_string().parse::<f64>().unwrap_or(f64::NAN));
609 }
610 result
611}
612
613#[derive(Debug, Clone, Copy, Default)]
623pub struct XPath10Evaluator;
624
625impl<N: DomNavigator> FunctionEvaluator<N> for XPath10Evaluator {
626 fn eval(
627 &self,
628 handle: FunctionHandle,
629 ctx: &mut DynamicContext<'_, N>,
630 args: Vec<XPathValue<N>>,
631 ) -> Result<XPathValue<N>, XPathError> {
632 let id = handle_to_function_id(handle)?;
633 match id {
634 FunctionId::String => {
636 use crate::xpath::atomize;
637 match args.len() {
638 0 => {
639 let item = ctx.require_context_item()?.clone();
640 let s = match item {
641 crate::xpath::iterator::XmlItem::Node(nav) => nav.value(),
642 crate::xpath::iterator::XmlItem::Atomic(v) => atomize::string_value(&v),
643 };
644 Ok(XPathValue::string(s))
645 }
646 1 => {
647 let s = atomize::to_string_10(&args[0]);
648 Ok(XPathValue::string(s))
649 }
650 _ => Err(XPathError::wrong_number_of_arguments(
651 "string",
652 1,
653 args.len(),
654 )),
655 }
656 }
657
658 FunctionId::Number => {
660 use crate::xpath::atomize;
661 match args.len() {
662 0 => {
663 let item = ctx.require_context_item()?.clone();
664 let d = match item {
665 crate::xpath::iterator::XmlItem::Node(nav) => {
666 let s = nav.value();
667 s.trim().parse().unwrap_or(f64::NAN)
668 }
669 crate::xpath::iterator::XmlItem::Atomic(v) => atomize::to_number(&v),
670 };
671 Ok(XPathValue::double(d))
672 }
673 1 => {
674 let d = atomize::to_number_10(&args[0]);
675 Ok(XPathValue::double(d))
676 }
677 _ => Err(XPathError::wrong_number_of_arguments(
678 "number",
679 1,
680 args.len(),
681 )),
682 }
683 }
684
685 FunctionId::Count
687 | FunctionId::StringLength
688 | FunctionId::Last
689 | FunctionId::Position => {
690 let result = eval_function(id, ctx, args)?;
691 Ok(wrap_as_double(result))
692 }
693
694 FunctionId::Sum | FunctionId::Floor | FunctionId::Ceiling | FunctionId::Round => {
696 let result = eval_function(id, ctx, args)?;
697 Ok(wrap_as_double(result))
698 }
699
700 _ => eval_function(id, ctx, args),
702 }
703 }
704}
705
706#[cfg(test)]
707mod tests {
708 use super::*;
709 use crate::xpath::RoXmlNavigator;
710
711 #[test]
712 fn test_function_handle_from_id() {
713 let handle = FunctionHandle::from(FunctionId::Count);
714 assert!(handle.is_builtin());
715 assert!(!handle.is_custom());
716 }
717
718 #[test]
719 fn test_custom_handle() {
720 let handle = FunctionHandle(CUSTOM_HANDLE_BASE);
721 assert!(!handle.is_builtin());
722 assert!(handle.is_custom());
723 assert_eq!(handle.custom_index(), Some(0));
724
725 let handle2 = FunctionHandle(CUSTOM_HANDLE_BASE + 5);
726 assert_eq!(handle2.custom_index(), Some(5));
727 }
728
729 #[test]
730 fn test_builtin_catalog_lookup() {
731 let catalog = BuiltinCatalog;
732 let handle = catalog.lookup("http://www.w3.org/2005/xpath-functions", "count", 1);
733 assert!(handle.is_some());
734 assert!(handle.unwrap().is_builtin());
735 }
736
737 #[test]
738 fn test_builtin_catalog_not_found() {
739 let catalog = BuiltinCatalog;
740 let handle = catalog.lookup("http://example.com", "my-func", 1);
741 assert!(handle.is_none());
742 }
743
744 #[test]
745 fn test_dynamic_signature_from_static() {
746 let catalog = BuiltinCatalog;
747 let handle = catalog
748 .lookup("http://www.w3.org/2005/xpath-functions", "count", 1)
749 .unwrap();
750 let sig = catalog.get_signature(handle);
751 assert!(sig.is_some());
752 let sig = sig.unwrap();
753 assert_eq!(&*sig.local_name, "count");
754 assert_eq!(sig.arity, FunctionArity::Exact(1));
755 }
756
757 #[test]
762 fn test_function_set_builtins_accessible() {
763 let functions: FunctionSet<RoXmlNavigator<'static>> = FunctionSet::with_builtins();
764
765 let handle = functions.lookup("http://www.w3.org/2005/xpath-functions", "count", 1);
767 assert!(handle.is_some());
768 assert!(handle.unwrap().is_builtin());
769 }
770
771 #[test]
772 fn test_function_set_register_custom() {
773 let mut functions: FunctionSet<RoXmlNavigator<'static>> = FunctionSet::with_builtins();
774
775 let sig = DynamicFunctionSignature::new(
776 "http://example.com/ext",
777 "my-func",
778 vec![SequenceType::string()],
779 SequenceType::string(),
780 );
781
782 let handle = functions.register(sig, |_ctx, _args| Ok(XPathValue::string("custom result")));
783
784 assert!(handle.is_custom());
785 assert_eq!(functions.custom_count(), 1);
786 }
787
788 #[test]
789 fn test_function_set_lookup_custom() {
790 let mut functions: FunctionSet<RoXmlNavigator<'static>> = FunctionSet::with_builtins();
791
792 let sig = DynamicFunctionSignature::new(
793 "http://example.com/ext",
794 "my-upper",
795 vec![SequenceType::string()],
796 SequenceType::string(),
797 );
798
799 let registered_handle = functions.register(sig, |_ctx, mut args| {
800 let s = super::super::atomize_to_string(args.remove(0))?;
801 Ok(XPathValue::string(s.to_uppercase()))
802 });
803
804 let found_handle = functions.lookup("http://example.com/ext", "my-upper", 1);
806 assert!(found_handle.is_some());
807 assert_eq!(found_handle.unwrap(), registered_handle);
808 assert!(found_handle.unwrap().is_custom());
809 }
810
811 #[test]
812 fn test_function_set_get_custom_signature() {
813 let mut functions: FunctionSet<RoXmlNavigator<'static>> = FunctionSet::with_builtins();
814
815 let sig = DynamicFunctionSignature::new(
816 "http://example.com/ext",
817 "test-func",
818 vec![SequenceType::integer(), SequenceType::integer()],
819 SequenceType::integer(),
820 );
821
822 let handle = functions.register(sig, |_ctx, _args| Ok(XPathValue::integer(42)));
823
824 let retrieved_sig = functions.get_signature(handle);
825 assert!(retrieved_sig.is_some());
826 let retrieved_sig = retrieved_sig.unwrap();
827 assert_eq!(&*retrieved_sig.namespace, "http://example.com/ext");
828 assert_eq!(&*retrieved_sig.local_name, "test-func");
829 assert_eq!(retrieved_sig.arity, FunctionArity::Exact(2));
830 }
831
832 #[test]
833 fn test_function_set_custom_overrides_builtin() {
834 let mut functions: FunctionSet<RoXmlNavigator<'static>> = FunctionSet::with_builtins();
835
836 let sig = DynamicFunctionSignature::new(
838 "http://www.w3.org/2005/xpath-functions",
839 "count",
840 vec![SequenceType::any()],
841 SequenceType::integer(),
842 );
843
844 let custom_handle = functions.register(sig, |_ctx, _args| {
845 Ok(XPathValue::integer(999))
847 });
848
849 let found_handle = functions.lookup("http://www.w3.org/2005/xpath-functions", "count", 1);
851 assert!(found_handle.is_some());
852 assert_eq!(found_handle.unwrap(), custom_handle);
853 assert!(found_handle.unwrap().is_custom());
854 }
855
856 #[test]
857 fn test_function_set_eval_custom() {
858 use crate::namespace::table::NameTable;
859 use crate::xpath::context::{DynamicContext, XPathContext};
860
861 let mut functions: FunctionSet<RoXmlNavigator<'static>> = FunctionSet::with_builtins();
862
863 let sig = DynamicFunctionSignature::new(
865 "http://example.com/ext",
866 "double",
867 vec![SequenceType::double()],
868 SequenceType::double(),
869 );
870
871 let handle = functions.register(sig, |_ctx, mut args| {
872 let val = args.remove(0);
873 let d = val.as_f64().unwrap_or(0.0);
874 Ok(XPathValue::double(d * 2.0))
875 });
876
877 let names = NameTable::new();
879 let static_ctx = XPathContext::new(&names);
880 let mut dyn_ctx: DynamicContext<'_, RoXmlNavigator<'static>> =
881 DynamicContext::new(&static_ctx, 0);
882
883 let args = vec![XPathValue::double(21.0)];
885 let result = functions.eval(handle, &mut dyn_ctx, args).unwrap();
886
887 assert_eq!(result.as_f64(), Some(42.0));
888 }
889
890 #[test]
891 fn test_function_set_eval_builtin() {
892 use crate::namespace::table::NameTable;
893 use crate::types::value::XmlValue;
894 use crate::xpath::context::{DynamicContext, XPathContext};
895 use crate::xpath::iterator::XmlItem;
896
897 let functions: FunctionSet<RoXmlNavigator<'static>> = FunctionSet::with_builtins();
898
899 let handle = functions
901 .lookup("http://www.w3.org/2005/xpath-functions", "count", 1)
902 .unwrap();
903
904 let names = NameTable::new();
906 let static_ctx = XPathContext::new(&names);
907 let mut dyn_ctx: DynamicContext<'_, RoXmlNavigator<'static>> =
908 DynamicContext::new(&static_ctx, 0);
909
910 let items = vec![
912 XmlItem::Atomic(XmlValue::integer(1.into())),
913 XmlItem::Atomic(XmlValue::integer(2.into())),
914 XmlItem::Atomic(XmlValue::integer(3.into())),
915 ];
916 let args = vec![XPathValue::from_sequence(items)];
917
918 let result = functions.eval(handle, &mut dyn_ctx, args).unwrap();
920 assert_eq!(
921 result.as_integer().map(|i| i.to_string()),
922 Some("3".to_string())
923 );
924 }
925
926 #[test]
927 fn test_function_set_range_arity() {
928 let mut functions: FunctionSet<RoXmlNavigator<'static>> = FunctionSet::with_builtins();
929
930 let sig = DynamicFunctionSignature::range(
932 "http://example.com/ext",
933 "multi",
934 1,
935 3,
936 vec![
937 SequenceType::string(),
938 SequenceType::string(),
939 SequenceType::string(),
940 ],
941 SequenceType::string(),
942 );
943
944 let handle =
945 functions.register(sig, |_ctx, args| Ok(XPathValue::integer(args.len() as i64)));
946
947 assert_eq!(
949 functions.lookup("http://example.com/ext", "multi", 1),
950 Some(handle)
951 );
952 assert_eq!(
953 functions.lookup("http://example.com/ext", "multi", 2),
954 Some(handle)
955 );
956 assert_eq!(
957 functions.lookup("http://example.com/ext", "multi", 3),
958 Some(handle)
959 );
960
961 assert!(functions
963 .lookup("http://example.com/ext", "multi", 0)
964 .is_none());
965 assert!(functions
966 .lookup("http://example.com/ext", "multi", 4)
967 .is_none());
968 }
969
970 #[test]
971 fn test_function_set_variadic() {
972 let mut functions: FunctionSet<RoXmlNavigator<'static>> = FunctionSet::with_builtins();
973
974 let sig = DynamicFunctionSignature::variadic(
976 "http://example.com/ext",
977 "varargs",
978 2,
979 vec![SequenceType::any_atomic()],
980 SequenceType::integer(),
981 );
982
983 let handle =
984 functions.register(sig, |_ctx, args| Ok(XPathValue::integer(args.len() as i64)));
985
986 assert_eq!(
988 functions.lookup("http://example.com/ext", "varargs", 2),
989 Some(handle)
990 );
991 assert_eq!(
992 functions.lookup("http://example.com/ext", "varargs", 5),
993 Some(handle)
994 );
995 assert_eq!(
996 functions.lookup("http://example.com/ext", "varargs", 100),
997 Some(handle)
998 );
999
1000 assert!(functions
1002 .lookup("http://example.com/ext", "varargs", 0)
1003 .is_none());
1004 assert!(functions
1005 .lookup("http://example.com/ext", "varargs", 1)
1006 .is_none());
1007 }
1008
1009 #[test]
1014 fn test_xpath10_catalog_resolves_core_function() {
1015 let catalog = XPath10Catalog;
1016 let handle = catalog.lookup("", "count", 1);
1018 assert!(handle.is_some());
1019 assert!(handle.unwrap().is_builtin());
1020 }
1021
1022 #[test]
1023 fn test_xpath10_catalog_rejects_non_core_function() {
1024 let catalog = XPath10Catalog;
1025 let handle = catalog.lookup("", "deep-equal", 2);
1027 assert!(handle.is_none());
1028 }
1029
1030 #[test]
1031 fn test_xpath10_catalog_non_empty_ns_delegates() {
1032 let catalog = XPath10Catalog;
1033 let handle = catalog.lookup("http://www.w3.org/2005/xpath-functions", "count", 1);
1035 assert!(handle.is_some());
1036 }
1037
1038 #[test]
1039 fn test_xpath10_catalog_all_core_functions() {
1040 let catalog = XPath10Catalog;
1041 let functions_with_arity: &[(&str, usize)] = &[
1043 ("last", 0),
1044 ("position", 0),
1045 ("count", 1),
1046 ("id", 1),
1047 ("name", 0),
1048 ("local-name", 0),
1049 ("namespace-uri", 0),
1050 ("lang", 1),
1051 ("string", 0),
1052 ("concat", 2),
1053 ("starts-with", 2),
1054 ("contains", 2),
1055 ("substring-before", 2),
1056 ("substring-after", 2),
1057 ("substring", 2),
1058 ("string-length", 0),
1059 ("normalize-space", 0),
1060 ("translate", 3),
1061 ("boolean", 1),
1062 ("not", 1),
1063 ("true", 0),
1064 ("false", 0),
1065 ("number", 0),
1066 ("sum", 1),
1067 ("floor", 1),
1068 ("ceiling", 1),
1069 ("round", 1),
1070 ];
1071 for (name, arity) in functions_with_arity {
1072 let handle = catalog.lookup("", name, *arity);
1073 assert!(
1074 handle.is_some(),
1075 "XPath 1.0 function '{}' with arity {} not found",
1076 name,
1077 arity
1078 );
1079 }
1080 }
1081
1082 #[test]
1087 fn test_xpath10_evaluator_count_returns_double() {
1088 use crate::namespace::table::NameTable;
1089 use crate::types::value::XmlValue;
1090 use crate::xpath::context::{DynamicContext, XPathContext};
1091 use crate::xpath::iterator::XmlItem;
1092
1093 let evaluator = XPath10Evaluator;
1094
1095 let names = NameTable::new();
1096 let static_ctx = XPathContext::new(&names);
1097 let mut dyn_ctx: DynamicContext<'_, RoXmlNavigator<'static>> =
1098 DynamicContext::new(&static_ctx, 0);
1099
1100 let items = vec![
1102 XmlItem::Atomic(XmlValue::integer(1.into())),
1103 XmlItem::Atomic(XmlValue::integer(2.into())),
1104 XmlItem::Atomic(XmlValue::integer(3.into())),
1105 ];
1106 let args = vec![XPathValue::from_sequence(items)];
1107 let handle = FunctionHandle::from(FunctionId::Count);
1108
1109 let result = evaluator.eval(handle, &mut dyn_ctx, args).unwrap();
1110 assert_eq!(result.as_f64(), Some(3.0));
1112 assert!(result.as_integer().is_none());
1114 }
1115
1116 #[test]
1117 fn test_xpath10_evaluator_string_with_node() {
1118 let evaluator = XPath10Evaluator;
1119
1120 let doc = roxmltree::Document::parse("<r><a>first</a><b>second</b></r>").unwrap();
1121 let mut nav_a = RoXmlNavigator::new(&doc);
1122 nav_a.move_to_first_child(); nav_a.move_to_first_child(); let mut nav_b = nav_a.clone();
1125 nav_b.move_to_next_sibling(); use crate::namespace::table::NameTable;
1128 use crate::xpath::context::{DynamicContext, XPathContext};
1129 use crate::xpath::iterator::XmlItem;
1130
1131 let names = NameTable::new();
1132 let static_ctx = XPathContext::new(&names);
1133 let mut dyn_ctx = DynamicContext::new(&static_ctx, 0);
1134
1135 let node_seq = XPathValue::from_sequence(vec![XmlItem::Node(nav_a), XmlItem::Node(nav_b)]);
1137 let args = vec![node_seq];
1138 let handle = FunctionHandle::from(FunctionId::String);
1139
1140 let result = evaluator.eval(handle, &mut dyn_ctx, args).unwrap();
1141 assert_eq!(result.as_str(), Some("first".to_string()));
1142 }
1143}