sxd_xpath/
context.rs

1//! Support for the various types of contexts before and during XPath
2//! evaluation.
3
4use sxd_document::QName;
5
6use std::collections::HashMap;
7use std::iter;
8
9use ::{Value, OwnedQName};
10use ::nodeset::{Node, OrderedNodes};
11use ::function;
12
13/// A mapping of names to XPath functions.
14type Functions = HashMap<OwnedQName, Box<function::Function + 'static>>;
15/// A mapping of names to XPath variables.
16type Variables<'d> = HashMap<OwnedQName, Value<'d>>;
17/// A mapping of namespace prefixes to namespace URIs.
18type Namespaces = HashMap<String, String>;
19
20/// Contains the context in which XPath expressions are executed. The
21/// context contains functions, variables, and namespace mappings.
22///
23/// ### Examples
24///
25/// A complete example showing all optional settings.
26///
27/// ```
28/// extern crate sxd_document;
29/// extern crate sxd_xpath;
30///
31/// use std::collections::HashMap;
32/// use sxd_document::parser;
33/// use sxd_xpath::{Factory, Context, Value};
34/// use sxd_xpath::{context, function};
35///
36/// struct Sigmoid;
37/// impl function::Function for Sigmoid {
38///     fn evaluate<'c, 'd>(&self,
39///                         _context: &context::Evaluation<'c, 'd>,
40///                         args: Vec<Value<'d>>)
41///                         -> Result<Value<'d>, function::Error>
42///     {
43///         let mut args = function::Args(args);
44///         args.exactly(1)?;
45///         let val = args.pop_number()?;
46///
47///         let computed = (1.0 + (-val).exp()).recip();
48///
49///         Ok(Value::Number(computed))
50///     }
51/// }
52///
53/// fn main() {
54///     let package = parser::parse("<thing xmlns:ns0='net:brain' ns0:bonus='1' />")
55///         .expect("failed to parse XML");
56///     let document = package.as_document();
57///     let node = document.root().children()[0];
58///
59///     let mut context = Context::new();
60///     context.set_function("sigmoid", Sigmoid);
61///     context.set_variable("t", 2.0);
62///     context.set_namespace("neural", "net:brain");
63///
64///     let xpath = "sigmoid(@neural:bonus + $t)";
65///
66///     let factory = Factory::new();
67///     let xpath = factory.build(xpath).expect("Could not compile XPath");
68///     let xpath = xpath.expect("No XPath was compiled");
69///
70///     let value = xpath.evaluate(&context, node).expect("XPath evaluation failed");
71///
72///     assert_eq!(0.952, (value.number() * 1000.0).trunc() / 1000.0);
73/// }
74/// ```
75///
76/// Note that we are using a custom function (`sigmoid`), a variable
77/// (`$t`), and a namespace (`neural:`). The current node is passed to
78/// the `evaluate` method and is not the root of the tree but the
79/// top-most element.
80///
81pub struct Context<'d> {
82    functions: Functions,
83    variables: Variables<'d>,
84    namespaces: Namespaces,
85}
86
87impl<'d> Context<'d> {
88    /// Registers the core XPath 1.0 functions.
89    pub fn new() -> Self {
90        let mut context = Self::without_core_functions();
91        function::register_core_functions(&mut context);
92        context
93    }
94
95    /// No functions, variables or namespaces will be defined.
96    pub fn without_core_functions() -> Self {
97        Context {
98            functions: Default::default(),
99            variables: Default::default(),
100            namespaces: Default::default(),
101        }
102    }
103
104    /// Register a function within the context
105    pub fn set_function<N, F>(&mut self, name: N, function: F)
106        where N: Into<OwnedQName>,
107              F: function::Function + 'static,
108    {
109        self.functions.insert(name.into(), Box::new(function));
110    }
111
112    /// Register a variable within the context
113    pub fn set_variable<N, V>(&mut self, name: N, value: V)
114        where N: Into<OwnedQName>,
115              V: Into<Value<'d>>,
116    {
117        self.variables.insert(name.into(), value.into());
118    }
119
120    /// Register a namespace prefix within the context
121    pub fn set_namespace(&mut self, prefix: &str, uri: &str) {
122        self.namespaces.insert(prefix.into(), uri.into());
123    }
124}
125
126impl<'d> Default for Context<'d> {
127    fn default() -> Self {
128        Context::new()
129    }
130}
131
132/// The context during evaluation of an XPath expression.
133///
134/// Clients of this library will use this when implementing custom
135/// functions.
136///
137/// # Lifetimes
138///
139/// We track two separate lifetimes: that of the user-provided context
140/// (`'c`) and that of the document (`'d`). This allows the
141/// user-provided context to live shorter than the document.
142#[derive(Copy, Clone)]
143pub struct Evaluation<'c, 'd: 'c> {
144    /// The context node
145    pub node: Node<'d>,
146    /// The context position
147    pub position: usize,
148    /// The context size
149    pub size: usize,
150    functions: &'c Functions,
151    variables: &'c Variables<'d>,
152    namespaces: &'c Namespaces,
153}
154
155impl<'c, 'd> Evaluation<'c, 'd> {
156    /// Prepares the context used while evaluating the XPath expression
157    pub fn new(context: &'c Context<'d>, node: Node<'d>) -> Evaluation<'c, 'd> {
158        Evaluation {
159            node: node,
160            functions: &context.functions,
161            variables: &context.variables,
162            namespaces: &context.namespaces,
163            position: 1,
164            size: 1,
165        }
166    }
167
168    /// Creates a new context node using the provided node
169    pub fn new_context_for<N>(&self, node: N) -> Evaluation<'c, 'd>
170        where N: Into<Node<'d>>
171    {
172        Evaluation {
173            node: node.into(),
174            .. *self
175        }
176    }
177
178    /// Looks up the function with the given name
179    pub fn function_for_name(&self, name: QName) -> Option<&'c function::Function> {
180        // FIXME: remove allocation
181        let name = name.into();
182        self.functions.get(&name).map(AsRef::as_ref)
183    }
184
185    /// Looks up the value of the variable
186    pub fn value_of(&self, name: QName) -> Option<&Value<'d>> {
187        // FIXME: remove allocation
188        let name = name.into();
189        self.variables.get(&name)
190    }
191
192    /// Looks up the namespace URI for the given prefix
193    pub fn namespace_for(&self, prefix: &str) -> Option<&str> {
194        self.namespaces.get(prefix).map(String::as_str)
195    }
196
197    /// Yields a new `Evaluation` context for each node in the nodeset.
198    pub fn new_contexts_for(self, nodes: OrderedNodes<'d>) -> EvaluationNodesetIter<'c, 'd> {
199        let sz = nodes.size();
200        EvaluationNodesetIter {
201            parent: self,
202            nodes: Vec::from(nodes).into_iter().enumerate(),
203            size: sz,
204        }
205    }
206}
207
208/// An iterator for the contexts of each node in a nodeset
209pub struct EvaluationNodesetIter<'c, 'd: 'c> {
210    parent: Evaluation<'c, 'd>,
211    nodes: iter::Enumerate<::std::vec::IntoIter<Node<'d>>>,
212    size: usize,
213}
214
215impl<'c, 'd> Iterator for EvaluationNodesetIter<'c, 'd> {
216    type Item = Evaluation<'c, 'd>;
217
218    fn next(&mut self) -> Option<Evaluation<'c, 'd>> {
219        self.nodes.next().map(|(idx, node)| {
220            Evaluation {
221                node: node,
222                position: idx + 1,
223                size: self.size,
224                .. self.parent
225            }
226        })
227    }
228}