skyscraper/xpath/grammar/expressions/sequence_expressions/
combining_node_sequences.rs

1//! https://www.w3.org/TR/2017/REC-xpath-31-20170321/#combining_seq
2
3use 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    // https://www.w3.org/TR/2017/REC-xpath-31-20170321/#prod-xpath31-UnionExpr
24
25    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        // Evaluate the first expression.
76        let result = self.expr.eval(context)?;
77
78        // If there's only one parameter, return it's eval.
79        if self.items.is_empty() {
80            return Ok(result);
81        }
82
83        // Otherwise, do the operation.
84        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    // https://www.w3.org/TR/2017/REC-xpath-31-20170321/#prod-xpath31-IntersectExceptExpr
116
117    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        // Evaluate the first expression.
167        let result = self.expr.eval(context)?;
168
169        // If there's only one parameter, return it's eval.
170        if self.items.is_empty() {
171            return Ok(result);
172        }
173
174        // Otherwise, do the operation.
175        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        // arrange
210        let input = "chapter|appendix";
211
212        // act
213        let (next_input, res) = union_expr(input).unwrap();
214
215        // assert
216        assert_eq!(res.to_string(), input);
217        assert_eq!(next_input, "");
218    }
219
220    #[test]
221    fn union_expr_should_parse_bar_whitespace() {
222        // arrange
223        let input = "chapter | appendix";
224
225        // act
226        let (next_input, res) = union_expr(input).unwrap();
227
228        // assert
229        assert_eq!(next_input, "");
230        assert_eq!(res.to_string(), "chapter|appendix");
231    }
232
233    #[test]
234    fn union_expr_should_parse_union() {
235        // arrange
236        let input = "chapter union appendix";
237
238        // act
239        let (next_input, res) = union_expr(input).unwrap();
240
241        // assert
242        assert_eq!(res.to_string(), input);
243        assert_eq!(next_input, "");
244    }
245
246    #[test]
247    fn union_expr_should_parse_intersect() {
248        // arrange
249        let input = "chapter intersect appendix";
250
251        // act
252        let (next_input, res) = union_expr(input).unwrap();
253
254        // assert
255        assert_eq!(res.to_string(), input);
256        assert_eq!(next_input, "");
257    }
258
259    #[test]
260    fn union_expr_should_parse_except() {
261        // arrange
262        let input = "chapter except appendix";
263
264        // act
265        let (next_input, res) = union_expr(input).unwrap();
266
267        // assert
268        assert_eq!(res.to_string(), input);
269        assert_eq!(next_input, "");
270    }
271}