skyscraper/xpath/grammar/expressions/sequence_expressions/
combining_node_sequences.rs1use std::fmt::Display;
4
5use nom::{
6 branch::alt, bytes::complete::tag, character::complete::multispace0, error::context,
7 multi::many0, sequence::tuple,
8};
9
10use crate::xpath::{
11 grammar::{
12 expressions::expressions_on_sequence_types::instance_of::{
13 instanceof_expr, InstanceofExpr,
14 },
15 recipes::Res,
16 terminal_symbols::symbol_separator,
17 },
18 xpath_item_set::XpathItemSet,
19 ExpressionApplyError, XpathExpressionContext,
20};
21
22pub fn union_expr(input: &str) -> Res<&str, UnionExpr> {
23 fn union_operator_map(input: &str) -> Res<&str, UnionExprOperatorType> {
26 tuple((symbol_separator, tag("union"), symbol_separator))(input)
27 .map(|(next_input, _res)| (next_input, UnionExprOperatorType::Union))
28 }
29
30 fn bar_operator_map(input: &str) -> Res<&str, UnionExprOperatorType> {
31 tuple((multispace0, tag("|"), multispace0))(input)
32 .map(|(next_input, _res)| (next_input, UnionExprOperatorType::Bar))
33 }
34
35 context(
36 "union_expr",
37 tuple((
38 intersect_except_expr,
39 many0(tuple((
40 alt((union_operator_map, bar_operator_map)),
41 intersect_except_expr,
42 ))),
43 )),
44 )(input)
45 .map(|(next_input, res)| {
46 let items = res
47 .1
48 .into_iter()
49 .map(|res| UnionExprPair(res.0, res.1))
50 .collect();
51 (next_input, UnionExpr { expr: res.0, items })
52 })
53}
54
55#[derive(PartialEq, Debug, Clone)]
56pub struct UnionExpr {
57 pub expr: IntersectExceptExpr,
58 pub items: Vec<UnionExprPair>,
59}
60
61#[derive(PartialEq, Debug, Clone)]
62pub struct UnionExprPair(UnionExprOperatorType, pub IntersectExceptExpr);
63
64impl Display for UnionExprPair {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 write!(f, "{}{}", self.0, self.1)
67 }
68}
69
70impl UnionExpr {
71 pub(crate) fn eval<'tree>(
72 &self,
73 context: &XpathExpressionContext<'tree>,
74 ) -> Result<XpathItemSet<'tree>, ExpressionApplyError> {
75 let result = self.expr.eval(context)?;
77
78 if self.items.is_empty() {
80 return Ok(result);
81 }
82
83 todo!("UnionExpr::eval union operator")
85 }
86}
87
88#[derive(PartialEq, Debug, Clone, Copy)]
89enum UnionExprOperatorType {
90 Union,
91 Bar,
92}
93
94impl Display for UnionExprOperatorType {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 match self {
97 UnionExprOperatorType::Union => write!(f, " union "),
98 UnionExprOperatorType::Bar => write!(f, "|"),
99 }
100 }
101}
102
103impl Display for UnionExpr {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 write!(f, "{}", self.expr)?;
106 for x in &self.items {
107 write!(f, "{}", x)?
108 }
109
110 Ok(())
111 }
112}
113
114fn intersect_except_expr(input: &str) -> Res<&str, IntersectExceptExpr> {
115 fn intersect(input: &str) -> Res<&str, IntersectExceptType> {
118 tuple((symbol_separator, tag("intersect"), symbol_separator))(input)
119 .map(|(next_input, _res)| (next_input, IntersectExceptType::Intersect))
120 }
121
122 fn except(input: &str) -> Res<&str, IntersectExceptType> {
123 tuple((symbol_separator, tag("except"), symbol_separator))(input)
124 .map(|(next_input, _res)| (next_input, IntersectExceptType::Except))
125 }
126
127 context(
128 "intersect_except_expr",
129 tuple((
130 instanceof_expr,
131 many0(tuple((alt((intersect, except)), instanceof_expr))),
132 )),
133 )(input)
134 .map(|(next_input, res)| {
135 let items = res
136 .1
137 .into_iter()
138 .map(|res| IntersectExceptPair(res.0, res.1))
139 .collect();
140 (next_input, IntersectExceptExpr { expr: res.0, items })
141 })
142}
143
144#[derive(PartialEq, Debug, Clone)]
145pub struct IntersectExceptExpr {
146 pub expr: InstanceofExpr,
147 pub items: Vec<IntersectExceptPair>,
148}
149
150impl Display for IntersectExceptExpr {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 write!(f, "{}", self.expr)?;
153 for x in &self.items {
154 write!(f, " {}", x)?
155 }
156
157 Ok(())
158 }
159}
160
161impl IntersectExceptExpr {
162 pub(crate) fn eval<'tree>(
163 &self,
164 context: &XpathExpressionContext<'tree>,
165 ) -> Result<XpathItemSet<'tree>, ExpressionApplyError> {
166 let result = self.expr.eval(context)?;
168
169 if self.items.is_empty() {
171 return Ok(result);
172 }
173
174 todo!("IntersectExceptExpr::eval intersect or except operator")
176 }
177}
178
179#[derive(PartialEq, Debug, Clone)]
180pub struct IntersectExceptPair(pub IntersectExceptType, pub InstanceofExpr);
181
182impl Display for IntersectExceptPair {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 write!(f, "{} {}", self.0, self.1)
185 }
186}
187
188#[derive(PartialEq, Debug, Clone)]
189pub enum IntersectExceptType {
190 Intersect,
191 Except,
192}
193
194impl Display for IntersectExceptType {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 match self {
197 IntersectExceptType::Intersect => write!(f, "intersect"),
198 IntersectExceptType::Except => write!(f, "except"),
199 }
200 }
201}
202
203#[cfg(test)]
204mod test {
205 use super::*;
206
207 #[test]
208 fn union_expr_should_parse_bar() {
209 let input = "chapter|appendix";
211
212 let (next_input, res) = union_expr(input).unwrap();
214
215 assert_eq!(res.to_string(), input);
217 assert_eq!(next_input, "");
218 }
219
220 #[test]
221 fn union_expr_should_parse_bar_whitespace() {
222 let input = "chapter | appendix";
224
225 let (next_input, res) = union_expr(input).unwrap();
227
228 assert_eq!(next_input, "");
230 assert_eq!(res.to_string(), "chapter|appendix");
231 }
232
233 #[test]
234 fn union_expr_should_parse_union() {
235 let input = "chapter union appendix";
237
238 let (next_input, res) = union_expr(input).unwrap();
240
241 assert_eq!(res.to_string(), input);
243 assert_eq!(next_input, "");
244 }
245
246 #[test]
247 fn union_expr_should_parse_intersect() {
248 let input = "chapter intersect appendix";
250
251 let (next_input, res) = union_expr(input).unwrap();
253
254 assert_eq!(res.to_string(), input);
256 assert_eq!(next_input, "");
257 }
258
259 #[test]
260 fn union_expr_should_parse_except() {
261 let input = "chapter except appendix";
263
264 let (next_input, res) = union_expr(input).unwrap();
266
267 assert_eq!(res.to_string(), input);
269 assert_eq!(next_input, "");
270 }
271}