xee_xpath/
queries.rs

1use std::rc::Rc;
2
3use xee_interpreter::{
4    context::{self, StaticContext},
5    error::SpannedResult as Result,
6};
7use xee_xpath_compiler::parse;
8
9use crate::query::{
10    Convert, ManyQuery, ManyRecurseQuery, OneQuery, OneRecurseQuery, OptionQuery,
11    OptionRecurseQuery, SequenceQuery,
12};
13
14/// A collection of XPath queries
15///
16/// You can register xpath expressions with conversion functions
17/// to turn the results into Rust values.
18#[derive(Debug, Default)]
19pub struct Queries<'a> {
20    pub(crate) default_static_context_builder: context::StaticContextBuilder<'a>,
21}
22
23impl<'a> Queries<'a> {
24    /// Construct a new collection of queries
25    ///
26    /// Supply a default static context builder, which is used
27    /// by default to construct a static context if none is supplied
28    /// explicitly.
29    pub fn new(default_static_context_builder: context::StaticContextBuilder<'a>) -> Self {
30        Self {
31            default_static_context_builder,
32        }
33    }
34
35    /// Construct a query that expects a single item result.
36    ///
37    /// This item is converted into a Rust value using supplied `convert` function.
38    ///
39    /// This uses a default static context.
40    pub fn one<V, F>(&self, s: &str, convert: F) -> Result<OneQuery<V, F>>
41    where
42        F: Convert<V>,
43    {
44        self.one_with_context(s, convert, self.default_static_context_builder.build())
45    }
46
47    /// Construct a query that expects a single item result.
48    ///
49    /// This item is converted into a Rust value using supplied `convert` function.
50    ///
51    /// You can supply a static context explicitly.
52    pub fn one_with_context<V, F>(
53        &self,
54        s: &str,
55        convert: F,
56        static_context: StaticContext,
57    ) -> Result<OneQuery<V, F>>
58    where
59        F: Convert<V>,
60    {
61        Ok(OneQuery {
62            program: Rc::new(parse(static_context, s)?),
63            convert,
64            phantom: std::marker::PhantomData,
65        })
66    }
67
68    /// Construct a query that expects a single item result.
69    ///
70    /// This item is converted into a Rust value not using a convert function
71    /// but through a recursive call that's passed in during execution.
72    ///
73    /// NOTE: recursion generally needs a stopping condition, but `one_recurse`
74    /// expects one value always - unlike `option_recurse` and `many_recurse`
75    /// which have the None or empty value. I think this means that
76    /// `one_recurse` is not in fact useful.
77    pub fn one_recurse(&self, s: &str) -> Result<OneRecurseQuery> {
78        self.one_recurse_with_context(s, self.default_static_context_builder.build())
79    }
80
81    /// Construct a query that expects a single item result, with explicit
82    /// static context.
83    pub fn one_recurse_with_context(
84        &self,
85        s: &str,
86        static_context: context::StaticContext,
87    ) -> Result<OneRecurseQuery> {
88        Ok(OneRecurseQuery {
89            program: Rc::new(parse(static_context, s)?),
90        })
91    }
92
93    /// Construct a query that expects an optional single item result.
94    ///
95    /// This item is converted into a Rust value using supplied `convert` function.
96    pub fn option<V, F>(&self, s: &str, convert: F) -> Result<OptionQuery<V, F>>
97    where
98        F: Convert<V>,
99    {
100        self.option_with_context(s, convert, self.default_static_context_builder.build())
101    }
102
103    /// Construct a query that expects an optional single item result with
104    /// explicit static context.
105    pub fn option_with_context<V, F>(
106        &self,
107        s: &str,
108        convert: F,
109        static_context: context::StaticContext,
110    ) -> Result<OptionQuery<V, F>>
111    where
112        F: Convert<V>,
113    {
114        Ok(OptionQuery {
115            program: Rc::new(parse(static_context, s)?),
116            convert,
117            phantom: std::marker::PhantomData,
118        })
119    }
120
121    /// Construct a recursive query that expects an optional single item result.
122    ///
123    /// This item is converted into a Rust value not using a convert
124    /// function but through a recursive call that's passed in during
125    /// execution.
126    pub fn option_recurse(&self, s: &str) -> Result<OptionRecurseQuery> {
127        self.option_recurse_with_context(s, self.default_static_context_builder.build())
128    }
129
130    /// Construct a recursive query that expects an optional single item result, with
131    /// explicit static context.
132    pub fn option_recurse_with_context(
133        &self,
134        s: &str,
135        static_context: context::StaticContext,
136    ) -> Result<OptionRecurseQuery> {
137        Ok(OptionRecurseQuery {
138            program: Rc::new(parse(static_context, s)?),
139        })
140    }
141
142    /// Construct a query that expects many items as a result.
143    ///
144    /// These items are converted into Rust values using supplied `convert` function.
145    pub fn many<V, F>(&self, s: &str, convert: F) -> Result<ManyQuery<V, F>>
146    where
147        F: Convert<V>,
148    {
149        self.many_with_context(s, convert, self.default_static_context_builder.build())
150    }
151
152    /// Construct a query that expects many items as a result, with explicit
153    /// static context.
154    pub fn many_with_context<V, F>(
155        &self,
156        s: &str,
157        convert: F,
158        static_context: context::StaticContext,
159    ) -> Result<ManyQuery<V, F>>
160    where
161        F: Convert<V>,
162    {
163        Ok(ManyQuery {
164            program: Rc::new(parse(static_context, s)?),
165            convert,
166            phantom: std::marker::PhantomData,
167        })
168    }
169
170    /// Construct a query that expects many items as a result.
171    ///
172    /// These items are converted into Rust values not using a convert
173    /// function but through a recursive call that's passed in during
174    /// execution.
175    pub fn many_recurse(&self, s: &str) -> Result<ManyRecurseQuery> {
176        self.many_recurse_with_context(s, self.default_static_context_builder.build())
177    }
178
179    /// Construct a recursive query that expects many items as a result, with explicit
180    /// static context.
181    pub fn many_recurse_with_context(
182        &self,
183        s: &str,
184        static_context: context::StaticContext,
185    ) -> Result<ManyRecurseQuery> {
186        Ok(ManyRecurseQuery {
187            program: Rc::new(parse(static_context, s)?),
188        })
189    }
190
191    /// Construct a query that gets a [`Sequence`] as a result.
192    ///
193    /// This is a low-level API that allows you to get the raw sequence
194    /// without converting it into Rust values.
195    pub fn sequence(&self, s: &str) -> Result<SequenceQuery> {
196        self.sequence_with_context(s, self.default_static_context_builder.build())
197    }
198
199    /// Construct a query that gets a [`Sequence`] as a result, with explicit
200    /// static context.
201    pub fn sequence_with_context(
202        &self,
203        s: &str,
204        static_context: context::StaticContext,
205    ) -> Result<SequenceQuery> {
206        Ok(SequenceQuery {
207            program: Rc::new(parse(static_context, s)?),
208        })
209    }
210}
211
212#[cfg(test)]
213mod tests {
214
215    use iri_string::types::IriStr;
216
217    use crate::{query::Query, Documents};
218
219    use super::*;
220
221    #[test]
222    fn test_one_query() -> Result<()> {
223        let mut documents = Documents::new();
224        let uri: &IriStr = "http://example.com".try_into().unwrap();
225        let doc = documents.add_string(uri, "<root>foo</root>").unwrap();
226
227        let queries = Queries::default();
228        let q = queries.one("/root/string()", |_, item| {
229            Ok(item.try_into_value::<String>()?)
230        })?;
231
232        let r = q.execute(&mut documents, doc)?;
233        assert_eq!(r, "foo");
234        Ok(())
235    }
236}