use std::fmt::Display;
use nom::{character::complete::char, error::context, multi::many0, sequence::tuple};
use crate::xpath::{
grammar::{recipes::Res, whitespace_recipes::ws},
xpath_item_set::XpathItemSet,
ExpressionApplyError, XpathExpressionContext,
};
use super::path_expressions::{path_expr, PathExpr};
pub fn simple_map_expr(input: &str) -> Res<&str, SimpleMapExpr> {
context(
"simple_map_expr",
tuple((path_expr, many0(ws((char('!'), path_expr))))),
)(input)
.map(|(next_input, res)| {
let expr = res.0;
let items = res.1.into_iter().map(|res| res.1).collect();
(next_input, SimpleMapExpr { expr, items })
})
}
#[derive(PartialEq, Debug, Clone)]
pub struct SimpleMapExpr {
pub expr: PathExpr,
pub items: Vec<PathExpr>,
}
impl Display for SimpleMapExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.expr)?;
for x in &self.items {
write!(f, "!{}", x)?;
}
Ok(())
}
}
impl SimpleMapExpr {
pub(crate) fn eval<'tree>(
&self,
context: &XpathExpressionContext<'tree>,
) -> Result<XpathItemSet<'tree>, ExpressionApplyError> {
let mut result = self.expr.eval(context)?;
if self.items.is_empty() {
return Ok(result);
}
for map_expr in &self.items {
let mut next_result = XpathItemSet::new();
let size = result.len();
for (i, item) in result.iter().enumerate() {
let inner_context =
context.new_with_item_and_size(item.clone(), i + 1, size, false);
let inner_result = map_expr.eval(&inner_context)?;
next_result.extend(inner_result);
}
result = next_result;
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_map_expr_should_parse() {
let input = "a!b!c";
let (next_input, res) = simple_map_expr(input).unwrap();
assert_eq!(next_input, "");
assert_eq!(res.to_string(), "a!b!c");
}
#[test]
fn simple_map_expr_should_parse_whitespace() {
let input = "a ! b ! c";
let (next_input, res) = simple_map_expr(input).unwrap();
assert_eq!(next_input, "");
assert_eq!(res.to_string(), "a!b!c");
}
#[test]
fn simple_map_expr_should_parse1() {
let input = r#"child::div1/child::para/string()!concat("id-", .)"#;
let (next_input, res) = simple_map_expr(input).unwrap();
assert_eq!(next_input, "");
assert_eq!(res.to_string(), input);
}
}