use std::rc::Rc;
use thiserror::Error;
use self::{
grammar::{data_model::XpathItem, xpath},
xpath_item_set::XpathItemSet,
};
pub mod grammar;
pub mod query;
pub mod xpath_item_set;
pub use self::grammar::{Xpath, XpathItemTree};
#[derive(PartialEq, Debug, Error)]
#[error("Error parsing expression: {msg}")]
pub struct ExpressionParseError {
msg: String,
}
pub fn parse(input: &str) -> Result<Xpath, ExpressionParseError> {
let (remaining, parsed) = xpath(input).map_err(|e| ExpressionParseError {
msg: format!("{}", e),
})?;
if !remaining.trim().is_empty() {
return Err(ExpressionParseError {
msg: format!("unexpected trailing input: {:?}", remaining),
});
}
Ok(parsed)
}
#[derive(PartialEq, Debug, Error)]
#[error("Error applying expression {msg}")]
pub struct ExpressionApplyError {
msg: String,
}
impl ExpressionApplyError {
pub(crate) fn new(msg: String) -> Self {
Self { msg }
}
}
#[derive(Debug)]
pub(crate) struct VariableScope<'tree> {
bindings: Vec<(String, XpathItemSet<'tree>)>,
parent: Option<Rc<VariableScope<'tree>>>,
}
impl<'tree> VariableScope<'tree> {
fn empty() -> Self {
Self {
bindings: Vec::new(),
parent: None,
}
}
fn get(&self, name: &str) -> Option<&XpathItemSet<'tree>> {
for (k, v) in self.bindings.iter().rev() {
if k == name {
return Some(v);
}
}
if let Some(parent) = &self.parent {
parent.get(name)
} else {
None
}
}
}
#[derive(Debug)]
pub(crate) struct XpathExpressionContext<'tree> {
item_tree: &'tree XpathItemTree,
item: XpathItem<'tree>,
position: usize,
size: usize,
is_initial_step: bool,
variables: Rc<VariableScope<'tree>>,
}
impl<'tree> XpathExpressionContext<'tree> {
pub fn new_single(
item_tree: &'tree XpathItemTree,
item: XpathItem<'tree>,
is_initial_step: bool,
) -> Self {
Self {
item_tree,
item,
position: 1,
size: 1,
is_initial_step,
variables: Rc::new(VariableScope::empty()),
}
}
pub fn new_with_variables(
&self,
items: &XpathItemSet<'tree>,
position: usize,
is_initial_step: bool,
) -> Self {
debug_assert!(position > 0, "XPath position is 1-based, got 0");
debug_assert!(
position <= items.len(),
"position {} exceeds items length {}",
position,
items.len()
);
Self {
item_tree: self.item_tree,
item: items[position - 1].clone(),
position,
size: items.len(),
is_initial_step,
variables: Rc::clone(&self.variables),
}
}
pub fn new_with_item_and_size(
&self,
item: XpathItem<'tree>,
position: usize,
size: usize,
is_initial_step: bool,
) -> Self {
Self {
item_tree: self.item_tree,
item,
position,
size,
is_initial_step,
variables: Rc::clone(&self.variables),
}
}
pub fn with_variable(
&self,
name: String,
value: XpathItemSet<'tree>,
) -> Self {
Self {
item_tree: self.item_tree,
item: self.item.clone(),
position: self.position,
size: self.size,
is_initial_step: self.is_initial_step,
variables: Rc::new(VariableScope {
bindings: vec![(name, value)],
parent: Some(Rc::clone(&self.variables)),
}),
}
}
pub fn get_variable(&self, name: &str) -> Option<&XpathItemSet<'tree>> {
self.variables.get(name)
}
pub fn with_variables_iter(
&self,
bindings: impl IntoIterator<Item = (String, XpathItemSet<'tree>)>,
) -> Self {
Self {
item_tree: self.item_tree,
item: self.item.clone(),
position: self.position,
size: self.size,
is_initial_step: self.is_initial_step,
variables: Rc::new(VariableScope {
bindings: bindings.into_iter().collect(),
parent: Some(Rc::clone(&self.variables)),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_should_handle_multiple_double_slashes() {
let xpath_text = r###"//hello//world"###;
let xpath = parse(xpath_text).unwrap();
assert_eq!(xpath.to_string(), xpath_text);
}
#[test]
fn parse_should_handle_reverse_step_after_double_slash() {
let xpath_text = r###"//hello//parent::world"###;
let xpath = parse(xpath_text).unwrap();
assert_eq!(xpath.to_string(), xpath_text);
}
}