Skip to main content

libxml_rs/exslt/
mod.rs

1//! EXSLT implementation — native Rust (§35).
2//!
3//! EXSLT is a community-driven set of extensions to XSLT 1.0. libxslt ships
4//! implementations of the following modules:
5//!
6//! - `exsl:` — Common (exsl:node-set, exsl:object-type, exsl:document)
7//! - `math:` — Math (math:max, math:min, math:sin, math:cos, ...)
8//! - `set:` — Sets (set:difference, set:distinct, set:intersection, ...)
9//! - `str:` — Strings (str:concat, str:padding, str:split, ...)
10//! - `dyn:` — Dynamic (dyn:element, dyn:attribute, dyn:evaluate, ...)
11//! - `func:` — Functions (func:function, func:result, func:script)
12//! - `date:` — Dates and Times (date:date, date:format-date, ...)
13//!
14//! # Registration model
15//!
16//! Upstream libxslt requires an explicit `exsltRegisterAll()` call (usually
17//! from the host application; `xsltproc` calls it at startup) before EXSLT
18//! functions are available. This module mirrors that model: a process-wide
19//! registry of EXSLT functions keyed by their full QName (e.g. `"math:max"`).
20//! `exsltRegisterAll()` populates the registry; each new transform context
21//! copies the registered functions into its XPath context (§31 integration).
22//!
23//! # EXSLT namespaces
24//!
25//! | Prefix | URI |
26//! |--------|-----|
27//! | exsl | `http://exslt.org/common` |
28//! | math | `http://exslt.org/math` |
29//! | set | `http://exslt.org/sets` |
30//! | str | `http://exslt.org/strings` |
31//! | dyn | `http://exslt.org/dynamic` |
32//! | func | `http://exslt.org/functions` |
33//! | date | `http://exslt.org/dates-and-times` |
34//!
35//! # Phase 9 status
36//!
37//! Complete: all seven modules implemented and registered.
38
39use once_cell::sync::Lazy;
40use parking_lot::Mutex;
41use std::collections::HashMap;
42
43use crate::xml::xpath::context::XPathContext;
44use crate::xml::xpath::types::XPathValue;
45
46pub mod common;
47pub mod dates;
48pub mod dynamic;
49pub mod functions;
50pub mod math;
51pub mod sets;
52pub mod strings;
53
54/// EXSLT namespace URIs.
55pub const EXSLT_NS_COMMON: &str = "http://exslt.org/common";
56pub const EXSLT_NS_MATH: &str = "http://exslt.org/math";
57pub const EXSLT_NS_SETS: &str = "http://exslt.org/sets";
58pub const EXSLT_NS_STRINGS: &str = "http://exslt.org/strings";
59pub const EXSLT_NS_DYNAMIC: &str = "http://exslt.org/dynamic";
60pub const EXSLT_NS_FUNCTIONS: &str = "http://exslt.org/functions";
61pub const EXSLT_NS_DATES: &str = "http://exslt.org/dates-and-times";
62
63/// The XPath function signature used throughout the EXSLT modules.
64pub type ExsltFunction = fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String>;
65
66/// A capture-capable EXSLT function (used by `func:function` bodies).
67///
68/// Stored as a leaked `&'static` reference so entries are `Copy` and the
69/// registry can hand out clones. The process-wide registry lives for the
70/// lifetime of the program, so leaking is intentional and bounded.
71pub type ExsltClosure =
72    &'static (dyn Fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String> + Send + Sync);
73
74/// Process-wide EXSLT function registry, keyed by full QName
75/// (e.g. `"math:max"`). Populated by `exsltRegisterAll()`.
76static REGISTRY: Lazy<Mutex<HashMap<String, ExsltClosure>>> =
77    Lazy::new(|| Mutex::new(HashMap::new()));
78
79/// Register a single EXSLT function under its full QName.
80pub fn register<F>(name: &str, f: F)
81where
82    F: Fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String> + Send + Sync + 'static,
83{
84    // SAFETY: the boxed closure is leaked; it lives for the process lifetime
85    // (the registry is never cleared) so the resulting 'static reference is
86    // sound.
87    let leaked: &'static (dyn Fn(&mut XPathContext, &[XPathValue]) -> Result<XPathValue, String>
88                  + Send
89                  + Sync) = Box::leak(Box::new(f));
90    REGISTRY.lock().insert(name.to_string(), leaked);
91}
92
93/// Look up a registered EXSLT function by full QName.
94pub fn lookup(name: &str) -> Option<ExsltClosure> {
95    REGISTRY.lock().get(name).copied()
96}
97
98/// Iterate over all registered EXSLT functions.
99pub fn iter_functions() -> Vec<(String, ExsltClosure)> {
100    REGISTRY
101        .lock()
102        .iter()
103        .map(|(k, v)| (k.clone(), *v))
104        .collect()
105}
106
107/// Whether any EXSLT functions have been registered.
108pub fn is_registered() -> bool {
109    !REGISTRY.lock().is_empty()
110}
111
112/// Register every EXSLT module (mirrors upstream `exsltRegisterAll`).
113pub fn register_all() {
114    common::register_all();
115    math::register_all();
116    sets::register_all();
117    strings::register_all();
118    dynamic::register_all();
119    dates::register_all();
120    functions::register_all();
121}
122
123/// The C ABI entry point: register all EXSLT modules.
124///
125/// # UPSTREAM-PARITY
126///
127/// ```c
128/// void exsltRegisterAll(void);
129/// ```
130///
131/// Oracle behavior: registers every EXSLT function so it becomes available
132/// to subsequently created transform contexts. Calling it twice is a no-op
133/// (re-registration overwrites identical entries).
134#[no_mangle]
135pub extern "C" fn exsltRegisterAll() {
136    register_all();
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_register_and_lookup() {
145        fn my_func(_ctx: &mut XPathContext, _args: &[XPathValue]) -> Result<XPathValue, String> {
146            Ok(XPathValue::String("hello".to_string()))
147        }
148        register("test:myfunc", my_func);
149        let f = lookup("test:myfunc");
150        assert!(f.is_some());
151        let (names, _) = iter_functions()
152            .into_iter()
153            .find(|(n, _)| n == "test:myfunc")
154            .unwrap();
155        assert_eq!(names, "test:myfunc");
156    }
157
158    #[test]
159    fn test_lookup_missing() {
160        assert!(lookup("nonexistent:fn").is_none());
161    }
162
163    #[test]
164    fn test_register_all_populates() {
165        // Register everything; all module functions must be present.
166        register_all();
167        for name in [
168            "exsl:node-set",
169            "exsl:object-type",
170            "math:max",
171            "math:sin",
172            "math:constant",
173            "set:difference",
174            "set:distinct",
175            "str:tokenize",
176            "str:padding",
177            "dyn:evaluate",
178            "date:date",
179            "date:date-time",
180        ] {
181            assert!(lookup(name).is_some(), "missing EXSLT function {}", name);
182        }
183    }
184}