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