Skip to main content

sup_xml_xslt/
extensions.rs

1//! User-supplied extension functions for XSLT / XPath.
2//!
3//! Stylesheets can reference functions in non-XSLT namespaces (e.g.
4//! `<xsl:value-of select="my:lookup($id)"/>` where `xmlns:my="urn:app"`).
5//! The XPath engine consults the [`ExtensionFunctions`] trait when
6//! it encounters such a call; if the trait's `call` returns `Some`,
7//! the engine uses that result.  Returning `None` signals "I don't
8//! handle this function" — the engine continues its fallback chain
9//! (native EXSLT, then "unknown function" error).
10//!
11//! Two integration paths:
12//!
13//! 1. **`Extensions` builder** — register individual functions by
14//!    `(namespace, name)`.  Best for the common case of a few
15//!    short helpers:
16//!
17//!    ```ignore
18//!    use sup_xml_xslt::{Extensions, XPathValue, Stylesheet};
19//!
20//!    let mut exts = Extensions::new();
21//!    exts.register("urn:app", "double", |args| {
22//!        let n = match args.first() {
23//!            Some(XPathValue::Number(n)) => *n,
24//!            Some(XPathValue::String(s)) => s.parse().unwrap_or(0.0),
25//!            _ => 0.0,
26//!        };
27//!        Ok(XPathValue::Number(n * 2.0))
28//!    });
29//!    let style = Stylesheet::compile_str(/* ... */).unwrap();
30//!    let result = style.apply_with_extensions(&source_doc, &exts)?;
31//!    ```
32//!
33//! 2. **Custom [`ExtensionFunctions`] impl** — for cases that need
34//!    state, dynamic dispatch over a wide function set, or
35//!    integration with an existing function table.  Implement the
36//!    trait directly on your own type.
37//!
38//! Both paths reach the same hook inside the XPath evaluator.
39
40use std::collections::HashMap;
41
42use sup_xml_core::error::XmlError;
43use sup_xml_core::xpath::XPathValue;
44
45/// Caller-supplied lookup for non-XSLT XPath functions.
46///
47/// Implement on your own type to integrate with an existing function
48/// registry, or use the [`Extensions`] builder for per-call closure
49/// registration.
50pub trait ExtensionFunctions {
51    /// Try to invoke `name` in `ns_uri` with `args`.  Return
52    /// `Some(Ok(value))` on success, `Some(Err(_))` to surface a
53    /// runtime error, or `None` if this implementation doesn't
54    /// handle the call (the engine will continue its fallback
55    /// chain — native EXSLT, then "unknown function" error).
56    fn call(
57        &self,
58        ns_uri: &str,
59        name:   &str,
60        args:   Vec<XPathValue>,
61    ) -> Option<Result<XPathValue, XmlError>>;
62}
63
64type ExtensionFn = Box<dyn Fn(Vec<XPathValue>) -> Result<XPathValue, XmlError>>;
65
66/// Ergonomic builder for the common case of registering a handful of
67/// closure-based extension functions keyed by `(namespace, name)`.
68///
69/// Lookups are O(1) and don't allocate per call (the &str argument is
70/// matched directly against the stored String keys via `Borrow`).
71#[derive(Default)]
72pub struct Extensions {
73    fns: HashMap<String, HashMap<String, ExtensionFn>>,
74}
75
76impl Extensions {
77    pub fn new() -> Self { Self::default() }
78
79    /// Register `f` to handle `ns_uri:name(...)` calls.  Re-registering
80    /// the same (ns, name) pair replaces the previous closure.
81    pub fn register<F>(
82        &mut self,
83        ns_uri: impl Into<String>,
84        name:   impl Into<String>,
85        f:      F,
86    ) -> &mut Self
87    where
88        F: Fn(Vec<XPathValue>) -> Result<XPathValue, XmlError> + 'static,
89    {
90        self.fns.entry(ns_uri.into())
91            .or_default()
92            .insert(name.into(), Box::new(f));
93        self
94    }
95}
96
97impl ExtensionFunctions for Extensions {
98    fn call(
99        &self,
100        ns_uri: &str,
101        name:   &str,
102        args:   Vec<XPathValue>,
103    ) -> Option<Result<XPathValue, XmlError>> {
104        let f = self.fns.get(ns_uri)?.get(name)?;
105        Some(f(args))
106    }
107}
108
109impl std::fmt::Debug for Extensions {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        let mut names: Vec<String> = Vec::new();
112        for (ns, m) in &self.fns {
113            for k in m.keys() {
114                names.push(format!("{{{ns}}}{k}"));
115            }
116        }
117        names.sort();
118        f.debug_struct("Extensions").field("registered", &names).finish()
119    }
120}