Skip to main content

xsd_schema/xpath/functions/
registry.rs

1//! XPath function registry.
2//!
3//! This module provides the `FunctionRegistry` for looking up function
4//! definitions by namespace, local name, and arity.
5
6use std::collections::HashMap;
7
8use once_cell::sync::Lazy;
9
10use super::signature::{FunctionArity, FunctionSignature, FN_2010_NAMESPACE, FN_NAMESPACE};
11use super::FunctionId;
12use crate::types::sequence::SequenceType;
13
14/// Key for function lookup in the registry.
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub struct FunctionKey {
17    /// Namespace URI
18    pub namespace: String,
19    /// Local name
20    pub local_name: String,
21    /// Number of arguments
22    pub arity: usize,
23}
24
25impl FunctionKey {
26    /// Create a new function key
27    pub fn new(namespace: impl Into<String>, local_name: impl Into<String>, arity: usize) -> Self {
28        Self {
29            namespace: namespace.into(),
30            local_name: local_name.into(),
31            arity,
32        }
33    }
34}
35
36/// Entry in the function registry combining ID and signature.
37#[derive(Debug, Clone)]
38pub struct FunctionEntry {
39    /// The function identifier for dispatch
40    pub id: FunctionId,
41    /// The function signature with type information
42    pub signature: FunctionSignature,
43}
44
45impl FunctionEntry {
46    /// Create a new function entry
47    pub fn new(id: FunctionId, signature: FunctionSignature) -> Self {
48        Self { id, signature }
49    }
50}
51
52/// Registry of all built-in XPath functions.
53///
54/// Provides lookup by namespace, local name, and arity.
55pub struct FunctionRegistry {
56    /// All registered function entries
57    entries: Vec<FunctionEntry>,
58    /// Lookup map from (namespace, local_name, arity) to entry index
59    lookup: HashMap<FunctionKey, usize>,
60    /// Lookup map for variadic functions: (namespace, local_name) -> entry index
61    /// Used when exact arity lookup fails
62    variadic_lookup: HashMap<(String, String), usize>,
63}
64
65impl FunctionRegistry {
66    /// Create a new empty registry
67    pub fn new() -> Self {
68        Self {
69            entries: Vec::new(),
70            lookup: HashMap::new(),
71            variadic_lookup: HashMap::new(),
72        }
73    }
74
75    /// Register a function entry
76    pub fn register(&mut self, entry: FunctionEntry) {
77        let index = self.entries.len();
78        let sig = &entry.signature;
79
80        // Register for each valid arity
81        match sig.arity {
82            FunctionArity::Exact(n) => {
83                let key =
84                    FunctionKey::new(sig.namespace.to_string(), sig.local_name.to_string(), n);
85                self.lookup.insert(key, index);
86            }
87            FunctionArity::Range(min, max) => {
88                for arity in min..=max {
89                    let key = FunctionKey::new(
90                        sig.namespace.to_string(),
91                        sig.local_name.to_string(),
92                        arity,
93                    );
94                    self.lookup.insert(key, index);
95                }
96            }
97            FunctionArity::Variadic(_) => {
98                // For variadic, register in the variadic lookup
99                self.variadic_lookup.insert(
100                    (sig.namespace.to_string(), sig.local_name.to_string()),
101                    index,
102                );
103            }
104        }
105
106        self.entries.push(entry);
107    }
108
109    /// Look up a function by namespace, local name, and arity.
110    ///
111    /// Also handles the XPath 2010 namespace alias.
112    pub fn lookup(
113        &self,
114        namespace: &str,
115        local_name: &str,
116        arity: usize,
117    ) -> Option<&FunctionEntry> {
118        // Try exact lookup first
119        let key = FunctionKey {
120            namespace: namespace.to_string(),
121            local_name: local_name.to_string(),
122            arity,
123        };
124        if let Some(&index) = self.lookup.get(&key) {
125            return Some(&self.entries[index]);
126        }
127
128        // Try variadic lookup
129        let variadic_key = (namespace.to_string(), local_name.to_string());
130        if let Some(&index) = self.variadic_lookup.get(&variadic_key) {
131            let entry = &self.entries[index];
132            if entry.signature.arity.matches(arity) {
133                return Some(entry);
134            }
135        }
136
137        // If namespace is the 2010 function namespace, try the standard namespace
138        if namespace == FN_2010_NAMESPACE {
139            return self.lookup(FN_NAMESPACE, local_name, arity);
140        }
141
142        None
143    }
144
145    /// Get an entry by its FunctionId.
146    pub fn by_id(&self, id: FunctionId) -> Option<&FunctionEntry> {
147        self.entries.iter().find(|e| e.id == id)
148    }
149
150    /// Get an entry by its FunctionId discriminant value.
151    ///
152    /// This is used internally for converting FunctionHandle back to FunctionId.
153    pub fn by_id_value(&self, value: u16) -> Option<&FunctionEntry> {
154        self.entries.iter().find(|e| (e.id as u16) == value)
155    }
156
157    /// Get a slice of all registered entries.
158    pub fn entries(&self) -> &[FunctionEntry] {
159        &self.entries
160    }
161
162    /// Get the number of registered functions
163    pub fn len(&self) -> usize {
164        self.entries.len()
165    }
166
167    /// Check if the registry is empty
168    pub fn is_empty(&self) -> bool {
169        self.entries.is_empty()
170    }
171}
172
173impl Default for FunctionRegistry {
174    fn default() -> Self {
175        Self::new()
176    }
177}
178
179// ============================================================================
180// Static Function Registry
181// ============================================================================
182
183/// The global function registry containing all built-in XPath 2.0 functions.
184pub static FUNCTION_REGISTRY: Lazy<FunctionRegistry> = Lazy::new(|| {
185    let mut registry = FunctionRegistry::new();
186    register_all_functions(&mut registry);
187    registry
188});
189
190/// Register all built-in functions in the registry.
191fn register_all_functions(registry: &mut FunctionRegistry) {
192    // Import type helpers
193    use super::signature::types::*;
194
195    // ========================================================================
196    // Boolean Functions
197    // ========================================================================
198    registry.register(FunctionEntry::new(
199        FunctionId::True,
200        FunctionSignature::new(FN_NAMESPACE, "true", vec![], boolean()),
201    ));
202    registry.register(FunctionEntry::new(
203        FunctionId::False,
204        FunctionSignature::new(FN_NAMESPACE, "false", vec![], boolean()),
205    ));
206    registry.register(FunctionEntry::new(
207        FunctionId::Not,
208        FunctionSignature::new(FN_NAMESPACE, "not", vec![item()], boolean()),
209    ));
210    registry.register(FunctionEntry::new(
211        FunctionId::Boolean,
212        FunctionSignature::new(FN_NAMESPACE, "boolean", vec![item()], boolean()),
213    ));
214
215    // ========================================================================
216    // Context Functions
217    // ========================================================================
218    registry.register(FunctionEntry::new(
219        FunctionId::Position,
220        FunctionSignature::new(FN_NAMESPACE, "position", vec![], integer()),
221    ));
222    registry.register(FunctionEntry::new(
223        FunctionId::Last,
224        FunctionSignature::new(FN_NAMESPACE, "last", vec![], integer()),
225    ));
226
227    // ========================================================================
228    // Sequence Functions
229    // ========================================================================
230    registry.register(FunctionEntry::new(
231        FunctionId::Empty,
232        FunctionSignature::new(FN_NAMESPACE, "empty", vec![any()], boolean()),
233    ));
234    registry.register(FunctionEntry::new(
235        FunctionId::Exists,
236        FunctionSignature::new(FN_NAMESPACE, "exists", vec![any()], boolean()),
237    ));
238    registry.register(FunctionEntry::new(
239        FunctionId::Count,
240        FunctionSignature::new(FN_NAMESPACE, "count", vec![any()], integer()),
241    ));
242    registry.register(FunctionEntry::new(
243        FunctionId::Reverse,
244        FunctionSignature::new(FN_NAMESPACE, "reverse", vec![any()], any()),
245    ));
246    registry.register(FunctionEntry::new(
247        FunctionId::ZeroOrOne,
248        FunctionSignature::new(FN_NAMESPACE, "zero-or-one", vec![any()], item_opt()),
249    ));
250    registry.register(FunctionEntry::new(
251        FunctionId::OneOrMore,
252        FunctionSignature::new(FN_NAMESPACE, "one-or-more", vec![any()], any()),
253    ));
254    registry.register(FunctionEntry::new(
255        FunctionId::ExactlyOne,
256        FunctionSignature::new(FN_NAMESPACE, "exactly-one", vec![any()], item()),
257    ));
258    registry.register(FunctionEntry::new(
259        FunctionId::DistinctValues,
260        FunctionSignature::range(
261            FN_NAMESPACE,
262            "distinct-values",
263            1,
264            2,
265            vec![any_atomic_star(), string()],
266            any_atomic_star(),
267        ),
268    ));
269    registry.register(FunctionEntry::new(
270        FunctionId::IndexOf,
271        FunctionSignature::range(
272            FN_NAMESPACE,
273            "index-of",
274            2,
275            3,
276            vec![any_atomic_star(), any_atomic(), string()],
277            integer_star(),
278        ),
279    ));
280    registry.register(FunctionEntry::new(
281        FunctionId::Remove,
282        FunctionSignature::new(FN_NAMESPACE, "remove", vec![any(), integer()], any()),
283    ));
284    registry.register(FunctionEntry::new(
285        FunctionId::InsertBefore,
286        FunctionSignature::new(
287            FN_NAMESPACE,
288            "insert-before",
289            vec![any(), integer(), any()],
290            any(),
291        ),
292    ));
293    registry.register(FunctionEntry::new(
294        FunctionId::Subsequence,
295        FunctionSignature::range(
296            FN_NAMESPACE,
297            "subsequence",
298            2,
299            3,
300            vec![any(), double(), double()],
301            any(),
302        ),
303    ));
304    registry.register(FunctionEntry::new(
305        FunctionId::Unordered,
306        FunctionSignature::new(FN_NAMESPACE, "unordered", vec![any()], any()),
307    ));
308    registry.register(FunctionEntry::new(
309        FunctionId::DeepEqual,
310        FunctionSignature::range(
311            FN_NAMESPACE,
312            "deep-equal",
313            2,
314            3,
315            vec![any(), any(), string()],
316            boolean(),
317        ),
318    ));
319
320    // ========================================================================
321    // Aggregate Functions
322    // ========================================================================
323    registry.register(FunctionEntry::new(
324        FunctionId::Sum,
325        FunctionSignature::range(
326            FN_NAMESPACE,
327            "sum",
328            1,
329            2,
330            vec![any_atomic_star(), any_atomic_opt()],
331            any_atomic(),
332        ),
333    ));
334    registry.register(FunctionEntry::new(
335        FunctionId::Avg,
336        FunctionSignature::new(
337            FN_NAMESPACE,
338            "avg",
339            vec![any_atomic_star()],
340            any_atomic_opt(),
341        ),
342    ));
343    registry.register(FunctionEntry::new(
344        FunctionId::Min,
345        FunctionSignature::range(
346            FN_NAMESPACE,
347            "min",
348            1,
349            2,
350            vec![any_atomic_star(), string()],
351            any_atomic_opt(),
352        ),
353    ));
354    registry.register(FunctionEntry::new(
355        FunctionId::Max,
356        FunctionSignature::range(
357            FN_NAMESPACE,
358            "max",
359            1,
360            2,
361            vec![any_atomic_star(), string()],
362            any_atomic_opt(),
363        ),
364    ));
365
366    // ========================================================================
367    // String Functions
368    // ========================================================================
369    registry.register(FunctionEntry::new(
370        FunctionId::Concat,
371        FunctionSignature::variadic(
372            FN_NAMESPACE,
373            "concat",
374            2,
375            vec![any_atomic_opt(), any_atomic_opt()],
376            string(),
377        ),
378    ));
379    registry.register(FunctionEntry::new(
380        FunctionId::StringJoin,
381        FunctionSignature::new(
382            FN_NAMESPACE,
383            "string-join",
384            vec![string_star(), string()],
385            string(),
386        ),
387    ));
388    registry.register(FunctionEntry::new(
389        FunctionId::Substring,
390        FunctionSignature::range(
391            FN_NAMESPACE,
392            "substring",
393            2,
394            3,
395            vec![string_opt(), double(), double()],
396            string(),
397        ),
398    ));
399    registry.register(FunctionEntry::new(
400        FunctionId::StringLength,
401        FunctionSignature::range(
402            FN_NAMESPACE,
403            "string-length",
404            0,
405            1,
406            vec![string_opt()],
407            integer(),
408        ),
409    ));
410    registry.register(FunctionEntry::new(
411        FunctionId::NormalizeSpace,
412        FunctionSignature::range(
413            FN_NAMESPACE,
414            "normalize-space",
415            0,
416            1,
417            vec![string_opt()],
418            string(),
419        ),
420    ));
421    registry.register(FunctionEntry::new(
422        FunctionId::NormalizeUnicode,
423        FunctionSignature::range(
424            FN_NAMESPACE,
425            "normalize-unicode",
426            1,
427            2,
428            vec![string_opt(), string()],
429            string(),
430        ),
431    ));
432    registry.register(FunctionEntry::new(
433        FunctionId::UpperCase,
434        FunctionSignature::new(FN_NAMESPACE, "upper-case", vec![string_opt()], string()),
435    ));
436    registry.register(FunctionEntry::new(
437        FunctionId::LowerCase,
438        FunctionSignature::new(FN_NAMESPACE, "lower-case", vec![string_opt()], string()),
439    ));
440    registry.register(FunctionEntry::new(
441        FunctionId::Translate,
442        FunctionSignature::new(
443            FN_NAMESPACE,
444            "translate",
445            vec![string_opt(), string(), string()],
446            string(),
447        ),
448    ));
449    registry.register(FunctionEntry::new(
450        FunctionId::EncodeForUri,
451        FunctionSignature::new(FN_NAMESPACE, "encode-for-uri", vec![string_opt()], string()),
452    ));
453    registry.register(FunctionEntry::new(
454        FunctionId::IriToUri,
455        FunctionSignature::new(FN_NAMESPACE, "iri-to-uri", vec![string_opt()], string()),
456    ));
457    registry.register(FunctionEntry::new(
458        FunctionId::EscapeHtmlUri,
459        FunctionSignature::new(
460            FN_NAMESPACE,
461            "escape-html-uri",
462            vec![string_opt()],
463            string(),
464        ),
465    ));
466    registry.register(FunctionEntry::new(
467        FunctionId::Contains,
468        FunctionSignature::range(
469            FN_NAMESPACE,
470            "contains",
471            2,
472            3,
473            vec![string_opt(), string_opt(), string()],
474            boolean(),
475        ),
476    ));
477    registry.register(FunctionEntry::new(
478        FunctionId::StartsWith,
479        FunctionSignature::range(
480            FN_NAMESPACE,
481            "starts-with",
482            2,
483            3,
484            vec![string_opt(), string_opt(), string()],
485            boolean(),
486        ),
487    ));
488    registry.register(FunctionEntry::new(
489        FunctionId::EndsWith,
490        FunctionSignature::range(
491            FN_NAMESPACE,
492            "ends-with",
493            2,
494            3,
495            vec![string_opt(), string_opt(), string()],
496            boolean(),
497        ),
498    ));
499    registry.register(FunctionEntry::new(
500        FunctionId::SubstringBefore,
501        FunctionSignature::range(
502            FN_NAMESPACE,
503            "substring-before",
504            2,
505            3,
506            vec![string_opt(), string_opt(), string()],
507            string(),
508        ),
509    ));
510    registry.register(FunctionEntry::new(
511        FunctionId::SubstringAfter,
512        FunctionSignature::range(
513            FN_NAMESPACE,
514            "substring-after",
515            2,
516            3,
517            vec![string_opt(), string_opt(), string()],
518            string(),
519        ),
520    ));
521    registry.register(FunctionEntry::new(
522        FunctionId::StringToCodepoints,
523        FunctionSignature::new(
524            FN_NAMESPACE,
525            "string-to-codepoints",
526            vec![string_opt()],
527            integer_star(),
528        ),
529    ));
530    registry.register(FunctionEntry::new(
531        FunctionId::CodepointsToString,
532        FunctionSignature::new(
533            FN_NAMESPACE,
534            "codepoints-to-string",
535            vec![integer_star()],
536            string(),
537        ),
538    ));
539    registry.register(FunctionEntry::new(
540        FunctionId::Compare,
541        FunctionSignature::range(
542            FN_NAMESPACE,
543            "compare",
544            2,
545            3,
546            vec![string_opt(), string_opt(), string()],
547            integer_opt(),
548        ),
549    ));
550    registry.register(FunctionEntry::new(
551        FunctionId::CodepointEqual,
552        FunctionSignature::new(
553            FN_NAMESPACE,
554            "codepoint-equal",
555            vec![string_opt(), string_opt()],
556            SequenceType::optional(crate::types::sequence::ItemType::AtomicType(
557                crate::types::XmlTypeCode::Boolean,
558            )),
559        ),
560    ));
561
562    // ========================================================================
563    // Numeric Functions
564    // ========================================================================
565    registry.register(FunctionEntry::new(
566        FunctionId::Abs,
567        FunctionSignature::new(FN_NAMESPACE, "abs", vec![numeric_opt()], numeric_opt()),
568    ));
569    registry.register(FunctionEntry::new(
570        FunctionId::Ceiling,
571        FunctionSignature::new(FN_NAMESPACE, "ceiling", vec![numeric_opt()], numeric_opt()),
572    ));
573    registry.register(FunctionEntry::new(
574        FunctionId::Floor,
575        FunctionSignature::new(FN_NAMESPACE, "floor", vec![numeric_opt()], numeric_opt()),
576    ));
577    registry.register(FunctionEntry::new(
578        FunctionId::Round,
579        FunctionSignature::new(FN_NAMESPACE, "round", vec![numeric_opt()], numeric_opt()),
580    ));
581    registry.register(FunctionEntry::new(
582        FunctionId::RoundHalfToEven,
583        FunctionSignature::range(
584            FN_NAMESPACE,
585            "round-half-to-even",
586            1,
587            2,
588            vec![numeric_opt(), integer()],
589            numeric_opt(),
590        ),
591    ));
592
593    // ========================================================================
594    // Node Functions
595    // ========================================================================
596    registry.register(FunctionEntry::new(
597        FunctionId::Name,
598        FunctionSignature::range(FN_NAMESPACE, "name", 0, 1, vec![node_opt()], string()),
599    ));
600    registry.register(FunctionEntry::new(
601        FunctionId::LocalName,
602        FunctionSignature::range(FN_NAMESPACE, "local-name", 0, 1, vec![node_opt()], string()),
603    ));
604    registry.register(FunctionEntry::new(
605        FunctionId::NamespaceUri,
606        FunctionSignature::range(
607            FN_NAMESPACE,
608            "namespace-uri",
609            0,
610            1,
611            vec![node_opt()],
612            any_uri(),
613        ),
614    ));
615    registry.register(FunctionEntry::new(
616        FunctionId::NodeName,
617        FunctionSignature::new(FN_NAMESPACE, "node-name", vec![node_opt()], qname_opt()),
618    ));
619    registry.register(FunctionEntry::new(
620        FunctionId::Nilled,
621        FunctionSignature::new(
622            FN_NAMESPACE,
623            "nilled",
624            vec![node_opt()],
625            SequenceType::optional(crate::types::sequence::ItemType::AtomicType(
626                crate::types::XmlTypeCode::Boolean,
627            )),
628        ),
629    ));
630    registry.register(FunctionEntry::new(
631        FunctionId::BaseUri,
632        FunctionSignature::range(
633            FN_NAMESPACE,
634            "base-uri",
635            0,
636            1,
637            vec![node_opt()],
638            any_uri_opt(),
639        ),
640    ));
641    registry.register(FunctionEntry::new(
642        FunctionId::DocumentUri,
643        FunctionSignature::new(
644            FN_NAMESPACE,
645            "document-uri",
646            vec![node_opt()],
647            any_uri_opt(),
648        ),
649    ));
650    registry.register(FunctionEntry::new(
651        FunctionId::Lang,
652        FunctionSignature::range(
653            FN_NAMESPACE,
654            "lang",
655            1,
656            2,
657            vec![string_opt(), node()],
658            boolean(),
659        ),
660    ));
661    registry.register(FunctionEntry::new(
662        FunctionId::Root,
663        FunctionSignature::range(FN_NAMESPACE, "root", 0, 1, vec![node_opt()], node_opt()),
664    ));
665    registry.register(FunctionEntry::new(
666        FunctionId::Id,
667        FunctionSignature::range(
668            FN_NAMESPACE,
669            "id",
670            1,
671            2,
672            vec![string_star(), node()],
673            SequenceType::star(crate::types::sequence::ItemType::AnyNode),
674        ),
675    ));
676    // fn:collection() / fn:collection($arg) — without an external default
677    // collection or a registered URI, both forms return the empty sequence
678    // (XPath 2.0 functions §15.5.6). This minimal stub is sufficient for
679    // CTA tests that probe `empty(collection())`.
680    registry.register(FunctionEntry::new(
681        FunctionId::Collection,
682        FunctionSignature::range(
683            FN_NAMESPACE,
684            "collection",
685            0,
686            1,
687            vec![string_opt()],
688            SequenceType::star(crate::types::sequence::ItemType::AnyNode),
689        ),
690    ));
691
692    // ========================================================================
693    // DateTime Functions
694    // ========================================================================
695    registry.register(FunctionEntry::new(
696        FunctionId::DateTime,
697        FunctionSignature::new(
698            FN_NAMESPACE,
699            "dateTime",
700            vec![date_opt(), time_opt()],
701            datetime_opt(),
702        ),
703    ));
704    registry.register(FunctionEntry::new(
705        FunctionId::CurrentDateTime,
706        FunctionSignature::new(FN_NAMESPACE, "current-dateTime", vec![], datetime()),
707    ));
708    registry.register(FunctionEntry::new(
709        FunctionId::CurrentDate,
710        FunctionSignature::new(FN_NAMESPACE, "current-date", vec![], date()),
711    ));
712    registry.register(FunctionEntry::new(
713        FunctionId::CurrentTime,
714        FunctionSignature::new(FN_NAMESPACE, "current-time", vec![], time()),
715    ));
716    registry.register(FunctionEntry::new(
717        FunctionId::ImplicitTimezone,
718        FunctionSignature::new(
719            FN_NAMESPACE,
720            "implicit-timezone",
721            vec![],
722            day_time_duration(),
723        ),
724    ));
725
726    // Duration component extraction
727    registry.register(FunctionEntry::new(
728        FunctionId::YearsFromDuration,
729        FunctionSignature::new(
730            FN_NAMESPACE,
731            "years-from-duration",
732            vec![duration_opt()],
733            integer_opt(),
734        ),
735    ));
736    registry.register(FunctionEntry::new(
737        FunctionId::MonthsFromDuration,
738        FunctionSignature::new(
739            FN_NAMESPACE,
740            "months-from-duration",
741            vec![duration_opt()],
742            integer_opt(),
743        ),
744    ));
745    registry.register(FunctionEntry::new(
746        FunctionId::DaysFromDuration,
747        FunctionSignature::new(
748            FN_NAMESPACE,
749            "days-from-duration",
750            vec![duration_opt()],
751            integer_opt(),
752        ),
753    ));
754    registry.register(FunctionEntry::new(
755        FunctionId::HoursFromDuration,
756        FunctionSignature::new(
757            FN_NAMESPACE,
758            "hours-from-duration",
759            vec![duration_opt()],
760            integer_opt(),
761        ),
762    ));
763    registry.register(FunctionEntry::new(
764        FunctionId::MinutesFromDuration,
765        FunctionSignature::new(
766            FN_NAMESPACE,
767            "minutes-from-duration",
768            vec![duration_opt()],
769            integer_opt(),
770        ),
771    ));
772    registry.register(FunctionEntry::new(
773        FunctionId::SecondsFromDuration,
774        FunctionSignature::new(
775            FN_NAMESPACE,
776            "seconds-from-duration",
777            vec![duration_opt()],
778            SequenceType::optional(crate::types::sequence::ItemType::AtomicType(
779                crate::types::XmlTypeCode::Decimal,
780            )),
781        ),
782    ));
783
784    // DateTime component extraction
785    registry.register(FunctionEntry::new(
786        FunctionId::YearFromDateTime,
787        FunctionSignature::new(
788            FN_NAMESPACE,
789            "year-from-dateTime",
790            vec![datetime_opt()],
791            integer_opt(),
792        ),
793    ));
794    registry.register(FunctionEntry::new(
795        FunctionId::MonthFromDateTime,
796        FunctionSignature::new(
797            FN_NAMESPACE,
798            "month-from-dateTime",
799            vec![datetime_opt()],
800            integer_opt(),
801        ),
802    ));
803    registry.register(FunctionEntry::new(
804        FunctionId::DayFromDateTime,
805        FunctionSignature::new(
806            FN_NAMESPACE,
807            "day-from-dateTime",
808            vec![datetime_opt()],
809            integer_opt(),
810        ),
811    ));
812    registry.register(FunctionEntry::new(
813        FunctionId::HoursFromDateTime,
814        FunctionSignature::new(
815            FN_NAMESPACE,
816            "hours-from-dateTime",
817            vec![datetime_opt()],
818            integer_opt(),
819        ),
820    ));
821    registry.register(FunctionEntry::new(
822        FunctionId::MinutesFromDateTime,
823        FunctionSignature::new(
824            FN_NAMESPACE,
825            "minutes-from-dateTime",
826            vec![datetime_opt()],
827            integer_opt(),
828        ),
829    ));
830    registry.register(FunctionEntry::new(
831        FunctionId::SecondsFromDateTime,
832        FunctionSignature::new(
833            FN_NAMESPACE,
834            "seconds-from-dateTime",
835            vec![datetime_opt()],
836            SequenceType::optional(crate::types::sequence::ItemType::AtomicType(
837                crate::types::XmlTypeCode::Decimal,
838            )),
839        ),
840    ));
841    registry.register(FunctionEntry::new(
842        FunctionId::TimezoneFromDateTime,
843        FunctionSignature::new(
844            FN_NAMESPACE,
845            "timezone-from-dateTime",
846            vec![datetime_opt()],
847            day_time_duration_opt(),
848        ),
849    ));
850
851    // Date component extraction
852    registry.register(FunctionEntry::new(
853        FunctionId::YearFromDate,
854        FunctionSignature::new(
855            FN_NAMESPACE,
856            "year-from-date",
857            vec![date_opt()],
858            integer_opt(),
859        ),
860    ));
861    registry.register(FunctionEntry::new(
862        FunctionId::MonthFromDate,
863        FunctionSignature::new(
864            FN_NAMESPACE,
865            "month-from-date",
866            vec![date_opt()],
867            integer_opt(),
868        ),
869    ));
870    registry.register(FunctionEntry::new(
871        FunctionId::DayFromDate,
872        FunctionSignature::new(
873            FN_NAMESPACE,
874            "day-from-date",
875            vec![date_opt()],
876            integer_opt(),
877        ),
878    ));
879    registry.register(FunctionEntry::new(
880        FunctionId::TimezoneFromDate,
881        FunctionSignature::new(
882            FN_NAMESPACE,
883            "timezone-from-date",
884            vec![date_opt()],
885            day_time_duration_opt(),
886        ),
887    ));
888
889    // Time component extraction
890    registry.register(FunctionEntry::new(
891        FunctionId::HoursFromTime,
892        FunctionSignature::new(
893            FN_NAMESPACE,
894            "hours-from-time",
895            vec![time_opt()],
896            integer_opt(),
897        ),
898    ));
899    registry.register(FunctionEntry::new(
900        FunctionId::MinutesFromTime,
901        FunctionSignature::new(
902            FN_NAMESPACE,
903            "minutes-from-time",
904            vec![time_opt()],
905            integer_opt(),
906        ),
907    ));
908    registry.register(FunctionEntry::new(
909        FunctionId::SecondsFromTime,
910        FunctionSignature::new(
911            FN_NAMESPACE,
912            "seconds-from-time",
913            vec![time_opt()],
914            SequenceType::optional(crate::types::sequence::ItemType::AtomicType(
915                crate::types::XmlTypeCode::Decimal,
916            )),
917        ),
918    ));
919    registry.register(FunctionEntry::new(
920        FunctionId::TimezoneFromTime,
921        FunctionSignature::new(
922            FN_NAMESPACE,
923            "timezone-from-time",
924            vec![time_opt()],
925            day_time_duration_opt(),
926        ),
927    ));
928
929    // Timezone adjustment
930    registry.register(FunctionEntry::new(
931        FunctionId::AdjustDateTimeToTimezone,
932        FunctionSignature::range(
933            FN_NAMESPACE,
934            "adjust-dateTime-to-timezone",
935            1,
936            2,
937            vec![datetime_opt(), day_time_duration_opt()],
938            datetime_opt(),
939        ),
940    ));
941    registry.register(FunctionEntry::new(
942        FunctionId::AdjustDateToTimezone,
943        FunctionSignature::range(
944            FN_NAMESPACE,
945            "adjust-date-to-timezone",
946            1,
947            2,
948            vec![date_opt(), day_time_duration_opt()],
949            date_opt(),
950        ),
951    ));
952    registry.register(FunctionEntry::new(
953        FunctionId::AdjustTimeToTimezone,
954        FunctionSignature::range(
955            FN_NAMESPACE,
956            "adjust-time-to-timezone",
957            1,
958            2,
959            vec![time_opt(), day_time_duration_opt()],
960            time_opt(),
961        ),
962    ));
963
964    // ========================================================================
965    // QName Functions
966    // ========================================================================
967    registry.register(FunctionEntry::new(
968        FunctionId::ResolveQName,
969        FunctionSignature::new(
970            FN_NAMESPACE,
971            "resolve-QName",
972            vec![string_opt(), element()],
973            qname_opt(),
974        ),
975    ));
976    registry.register(FunctionEntry::new(
977        FunctionId::QName,
978        FunctionSignature::new(FN_NAMESPACE, "QName", vec![string_opt(), string()], qname()),
979    ));
980    registry.register(FunctionEntry::new(
981        FunctionId::PrefixFromQName,
982        FunctionSignature::new(
983            FN_NAMESPACE,
984            "prefix-from-QName",
985            vec![qname_opt()],
986            string_opt(),
987        ),
988    ));
989    registry.register(FunctionEntry::new(
990        FunctionId::LocalNameFromQName,
991        FunctionSignature::new(
992            FN_NAMESPACE,
993            "local-name-from-QName",
994            vec![qname_opt()],
995            string_opt(),
996        ),
997    ));
998    registry.register(FunctionEntry::new(
999        FunctionId::NamespaceUriFromQName,
1000        FunctionSignature::new(
1001            FN_NAMESPACE,
1002            "namespace-uri-from-QName",
1003            vec![qname_opt()],
1004            any_uri_opt(),
1005        ),
1006    ));
1007    registry.register(FunctionEntry::new(
1008        FunctionId::NamespaceUriForPrefix,
1009        FunctionSignature::new(
1010            FN_NAMESPACE,
1011            "namespace-uri-for-prefix",
1012            vec![string_opt(), element()],
1013            any_uri_opt(),
1014        ),
1015    ));
1016    registry.register(FunctionEntry::new(
1017        FunctionId::InScopePrefixes,
1018        FunctionSignature::new(
1019            FN_NAMESPACE,
1020            "in-scope-prefixes",
1021            vec![element()],
1022            string_star(),
1023        ),
1024    ));
1025
1026    // ========================================================================
1027    // URI Functions
1028    // ========================================================================
1029    registry.register(FunctionEntry::new(
1030        FunctionId::ResolveUri,
1031        FunctionSignature::range(
1032            FN_NAMESPACE,
1033            "resolve-uri",
1034            1,
1035            2,
1036            vec![string_opt(), string()],
1037            any_uri_opt(),
1038        ),
1039    ));
1040    registry.register(FunctionEntry::new(
1041        FunctionId::StaticBaseUri,
1042        FunctionSignature::new(FN_NAMESPACE, "static-base-uri", vec![], any_uri_opt()),
1043    ));
1044
1045    // ========================================================================
1046    // Regex Functions
1047    // ========================================================================
1048    registry.register(FunctionEntry::new(
1049        FunctionId::Matches,
1050        FunctionSignature::range(
1051            FN_NAMESPACE,
1052            "matches",
1053            2,
1054            3,
1055            vec![string_opt(), string(), string()],
1056            boolean(),
1057        ),
1058    ));
1059    registry.register(FunctionEntry::new(
1060        FunctionId::Replace,
1061        FunctionSignature::range(
1062            FN_NAMESPACE,
1063            "replace",
1064            3,
1065            4,
1066            vec![string_opt(), string(), string(), string()],
1067            string(),
1068        ),
1069    ));
1070    registry.register(FunctionEntry::new(
1071        FunctionId::Tokenize,
1072        FunctionSignature::range(
1073            FN_NAMESPACE,
1074            "tokenize",
1075            2,
1076            3,
1077            vec![string_opt(), string(), string()],
1078            string_star(),
1079        ),
1080    ));
1081
1082    // ========================================================================
1083    // Special Functions
1084    // ========================================================================
1085    registry.register(FunctionEntry::new(
1086        FunctionId::Trace,
1087        FunctionSignature::range(FN_NAMESPACE, "trace", 1, 2, vec![any(), string()], any()),
1088    ));
1089    registry.register(FunctionEntry::new(
1090        FunctionId::Data,
1091        FunctionSignature::new(FN_NAMESPACE, "data", vec![any()], any_atomic_star()),
1092    ));
1093    registry.register(FunctionEntry::new(
1094        FunctionId::DefaultCollation,
1095        FunctionSignature::new(FN_NAMESPACE, "default-collation", vec![], string()),
1096    ));
1097
1098    // ========================================================================
1099    // Conversion Functions
1100    // ========================================================================
1101    registry.register(FunctionEntry::new(
1102        FunctionId::String,
1103        FunctionSignature::range(FN_NAMESPACE, "string", 0, 1, vec![item_opt()], string()),
1104    ));
1105    registry.register(FunctionEntry::new(
1106        FunctionId::Number,
1107        FunctionSignature::range(
1108            FN_NAMESPACE,
1109            "number",
1110            0,
1111            1,
1112            vec![any_atomic_opt()],
1113            double(),
1114        ),
1115    ));
1116}
1117
1118#[cfg(test)]
1119mod tests {
1120    use super::*;
1121
1122    #[test]
1123    fn test_registry_lookup() {
1124        let entry = FUNCTION_REGISTRY.lookup(FN_NAMESPACE, "count", 1);
1125        assert!(entry.is_some());
1126        let entry = entry.unwrap();
1127        assert_eq!(entry.id, FunctionId::Count);
1128    }
1129
1130    #[test]
1131    fn test_registry_lookup_not_found() {
1132        let entry = FUNCTION_REGISTRY.lookup(FN_NAMESPACE, "nonexistent", 1);
1133        assert!(entry.is_none());
1134    }
1135
1136    #[test]
1137    fn test_registry_lookup_wrong_arity() {
1138        let entry = FUNCTION_REGISTRY.lookup(FN_NAMESPACE, "count", 2);
1139        assert!(entry.is_none());
1140    }
1141
1142    #[test]
1143    fn test_registry_lookup_range_arity() {
1144        // substring has arity 2-3
1145        let entry2 = FUNCTION_REGISTRY.lookup(FN_NAMESPACE, "substring", 2);
1146        let entry3 = FUNCTION_REGISTRY.lookup(FN_NAMESPACE, "substring", 3);
1147        assert!(entry2.is_some());
1148        assert!(entry3.is_some());
1149        assert_eq!(entry2.unwrap().id, FunctionId::Substring);
1150        assert_eq!(entry3.unwrap().id, FunctionId::Substring);
1151    }
1152
1153    #[test]
1154    fn test_registry_lookup_variadic() {
1155        // concat is variadic with min 2
1156        let entry2 = FUNCTION_REGISTRY.lookup(FN_NAMESPACE, "concat", 2);
1157        let entry5 = FUNCTION_REGISTRY.lookup(FN_NAMESPACE, "concat", 5);
1158        let entry1 = FUNCTION_REGISTRY.lookup(FN_NAMESPACE, "concat", 1);
1159
1160        assert!(entry2.is_some());
1161        assert!(entry5.is_some());
1162        assert!(entry1.is_none()); // Less than min
1163
1164        assert_eq!(entry2.unwrap().id, FunctionId::Concat);
1165        assert_eq!(entry5.unwrap().id, FunctionId::Concat);
1166    }
1167
1168    #[test]
1169    fn test_registry_2010_namespace_alias() {
1170        let entry = FUNCTION_REGISTRY.lookup(FN_2010_NAMESPACE, "count", 1);
1171        assert!(entry.is_some());
1172        assert_eq!(entry.unwrap().id, FunctionId::Count);
1173    }
1174
1175    #[test]
1176    fn test_registry_by_id() {
1177        let entry = FUNCTION_REGISTRY.by_id(FunctionId::Position);
1178        assert!(entry.is_some());
1179        assert_eq!(entry.unwrap().signature.local_name, "position");
1180    }
1181
1182    #[test]
1183    fn test_registry_size() {
1184        // We register many functions, check it's not empty
1185        assert!(!FUNCTION_REGISTRY.is_empty());
1186        assert!(FUNCTION_REGISTRY.len() > 50);
1187    }
1188}