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}