xee_interpreter/context/
static_context_builder.rs

1use ahash::HashMap;
2use iri_string::types::IriAbsoluteString;
3use xee_name::Namespaces;
4use xot::xmlname::OwnedName;
5
6use crate::context;
7
8#[derive(Debug, Clone, Default)]
9pub struct StaticContextBuilder<'a> {
10    variable_names: Vec<OwnedName>,
11    namespaces: HashMap<&'a str, &'a str>,
12    default_element_namespace: &'a str,
13    default_function_namespace: &'a str,
14    static_base_uri: Option<IriAbsoluteString>,
15}
16
17impl<'a> StaticContextBuilder<'a> {
18    /// Set the variable names that the XPath expression expects.
19    ///
20    /// They should be supplied in the order that they would be passed into if
21    /// the XPath expression was a function.
22    ///
23    /// Calling this multiple times will override the variable names.
24    pub fn variable_names(
25        &mut self,
26        variable_names: impl IntoIterator<Item = OwnedName>,
27    ) -> &mut Self {
28        self.variable_names = variable_names.into_iter().collect();
29        self
30    }
31
32    /// Set the namespace prefixes that the XPath expression can use.
33    ///
34    /// This is an iterable of tuples where the first element is the prefix and
35    /// the second element is the namespace URI.
36    ///
37    /// If a prefix is empty, it sets the default namespace.
38    ///
39    /// Calling this multiple times will override the namespaces.
40    pub fn namespaces(
41        &mut self,
42        namespaces: impl IntoIterator<Item = (&'a str, &'a str)>,
43    ) -> &mut Self {
44        for (prefix, uri) in namespaces {
45            self.add_namespace(prefix, uri);
46        }
47        self
48    }
49
50    /// Add a namespace prefix that the XPath expression can use.
51    pub fn add_namespace(&mut self, prefix: &'a str, uri: &'a str) -> &mut Self {
52        if prefix.is_empty() {
53            self.default_element_namespace = uri;
54        } else {
55            self.namespaces.insert(prefix, uri);
56        }
57        self
58    }
59
60    /// Set the default namespace for element references in the XPath expression.
61    pub fn default_element_namespace(&mut self, default_element_namespace: &'a str) -> &mut Self {
62        self.default_element_namespace = default_element_namespace;
63        self
64    }
65
66    /// Set the default namespace for function references in the XPath expression.
67    pub fn default_function_namespace(&mut self, default_function_namespace: &'a str) -> &mut Self {
68        self.default_function_namespace = default_function_namespace;
69        self
70    }
71
72    /// Set the static base URI
73    pub fn static_base_uri(&mut self, static_base_uri: Option<IriAbsoluteString>) -> &mut Self {
74        self.static_base_uri = static_base_uri;
75        self
76    }
77
78    /// Build the static context.
79    ///
80    /// This will always include the default known namespaces for
81    /// XPath, and the default function namespace will be the `fn` namespace
82    /// if not set.
83    pub fn build(&self) -> context::StaticContext {
84        let mut namespaces = Namespaces::default_namespaces();
85        for (prefix, uri) in &self.namespaces {
86            namespaces.insert(prefix.to_string(), uri.to_string());
87        }
88        let default_function_namespace = if !self.default_function_namespace.is_empty() {
89            self.default_function_namespace
90        } else {
91            Namespaces::FN_NAMESPACE
92        };
93        let namespaces = xee_name::Namespaces::new(
94            namespaces,
95            self.default_element_namespace.to_string(),
96            default_function_namespace.to_string(),
97        );
98        let variable_names = self.variable_names.clone().into_iter().collect();
99        context::StaticContext::new(namespaces, variable_names, self.static_base_uri.clone())
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use ahash::HashSet;
106
107    use super::*;
108
109    #[test]
110    fn test_variable_names() {
111        let mut builder = StaticContextBuilder::default();
112        let foo = OwnedName::new("foo".to_string(), "".to_string(), "".to_string());
113        let bar = OwnedName::new("bar".to_string(), "".to_string(), "".to_string());
114        builder.variable_names([foo.clone(), bar.clone()]);
115        assert_eq!(builder.variable_names, vec![foo, bar]);
116    }
117
118    #[test]
119    fn test_default_behavior() {
120        let builder = StaticContextBuilder::default();
121        let static_context = builder.build();
122        assert_eq!(static_context.namespaces().default_element_namespace(), "");
123        assert_eq!(
124            static_context.namespaces().default_function_namespace,
125            Namespaces::FN_NAMESPACE
126        );
127        assert_eq!(static_context.variable_names(), &HashSet::default());
128        assert_eq!(
129            static_context.namespaces().by_prefix("xml"),
130            Some("http://www.w3.org/XML/1998/namespace")
131        );
132    }
133}