diff --git a/src/axis.rs b/src/axis.rs index f8ea7737..4070dbb0 100644 --- a/src/axis.rs +++ b/src/axis.rs @@ -212,11 +212,12 @@ mod test { use crate::context::{self, Context}; use crate::node_test::NodeTest; use crate::nodeset::{Node, OrderedNodes}; + use crate::visitor::{Visitable, Visitor}; use super::Axis::*; use super::*; - #[derive(Debug)] + #[derive(Debug, Clone)] struct DummyNodeTest; impl NodeTest for DummyNodeTest { fn test<'c, 'd>( @@ -226,6 +227,13 @@ mod test { ) { result.add(context.node) } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + } + impl Visitable for DummyNodeTest { + fn visit(&self, _visitor: &mut dyn Visitor) {} } fn execute<'n, N>(axis: Axis, node: N) -> OrderedNodes<'n> diff --git a/src/expression.rs b/src/expression.rs index 1cb2f0d6..c900bc57 100644 --- a/src/expression.rs +++ b/src/expression.rs @@ -8,6 +8,7 @@ use crate::context; use crate::function; use crate::node_test::NodeTest; use crate::nodeset::{Nodeset, OrderedNodes}; +use crate::visitor::{Visitable, Visitor}; use crate::Value::{Boolean, Number}; use crate::{LiteralValue, OwnedPrefixedName, Value}; @@ -46,7 +47,7 @@ fn value_into_ordered_nodes(v: Value<'_>) -> Result, Error> { } } -pub trait Expression: fmt::Debug { +pub trait Expression: fmt::Debug + Visitable { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error>; } @@ -79,6 +80,12 @@ pub struct And { binary_constructor!(And); +impl Visitable for And { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_and(&self.left, &self.right); + } +} + impl Expression for And { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { let left = self.left.evaluate(context)?.boolean(); @@ -91,6 +98,12 @@ impl Expression for And { #[derive(Debug)] pub struct ContextNode; +impl Visitable for ContextNode { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_context_node(); + } +} + impl Expression for ContextNode { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { Ok(Value::Nodeset(nodeset![context.node])) @@ -150,6 +163,12 @@ impl Equal { } } +impl Visitable for Equal { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_equal(&self.left, &self.right); + } +} + impl Expression for Equal { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { self.boolean_evaluate(context).map(Boolean) @@ -169,6 +188,12 @@ impl NotEqual { } } +impl Visitable for NotEqual { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_not_equal(&self.equal.left, &self.equal.right); + } +} + impl Expression for NotEqual { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { self.equal.boolean_evaluate(context).map(|v| Boolean(!v)) @@ -181,6 +206,12 @@ pub struct Function { pub arguments: Vec, } +impl Visitable for Function { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_function(&self.name, &self.arguments); + } +} + impl Expression for Function { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { let name = resolve_prefixed_name(context, &self.name)?; @@ -209,6 +240,12 @@ impl From for Literal { } } +impl Visitable for Literal { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_literal(&self.value); + } +} + impl Expression for Literal { fn evaluate<'c, 'd>(&self, _: &context::Evaluation<'c, 'd>) -> Result, Error> { Ok(self.value.clone()) @@ -279,6 +316,12 @@ impl Math { } } +impl Visitable for Math { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_math(&self.left, &self.right, &self.operation); + } +} + impl Expression for Math { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { let left = self.left.evaluate(context)?; @@ -303,6 +346,12 @@ pub struct Negation { pub expression: SubExpression, } +impl Visitable for Negation { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_negation(&self.expression); + } +} + impl Expression for Negation { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { self.expression @@ -319,6 +368,12 @@ pub struct Or { binary_constructor!(Or); +impl Visitable for Or { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_or(&self.left, &self.right); + } +} + impl Expression for Or { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { let left = self.left.evaluate(context)?.boolean(); @@ -339,6 +394,12 @@ impl Path { } } +impl Visitable for Path { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_path(&self.start_point, &self.steps); + } +} + impl Expression for Path { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { let result = self.start_point.evaluate(context)?; @@ -370,6 +431,12 @@ impl Filter { } } +impl Visitable for Filter { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_filter(&self.node_selector, &self.predicate); + } +} + impl Expression for Filter { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { self.node_selector @@ -433,6 +500,12 @@ impl Relational { } } +impl Visitable for Relational { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_relational(&self.left, &self.right, &self.operation); + } +} + impl Expression for Relational { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { let left_val = self.left.evaluate(context)?; @@ -456,6 +529,12 @@ impl fmt::Debug for Relational { #[derive(Debug)] pub struct RootNode; +impl Visitable for RootNode { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_root_node(); + } +} + impl Expression for RootNode { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { Ok(Value::Nodeset(nodeset![context.node.document().root()])) @@ -463,7 +542,7 @@ impl Expression for RootNode { } #[derive(Debug)] -struct Predicate { +pub struct Predicate { pub expression: SubExpression, } @@ -505,6 +584,12 @@ pub struct ParameterizedStep { predicates: Vec, } +impl Visitable for Step { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_step(&self.axis, &self.node_test, &self.predicates); + } +} + impl ParameterizedStep where A: AxisLike, @@ -561,6 +646,12 @@ pub struct Union { binary_constructor!(Union); +impl Visitable for Union { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_union(&self.left, &self.right); + } +} + impl Expression for Union { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { let as_nodes = |e: &SubExpression| e.evaluate(context).and_then(value_into_nodeset); @@ -594,6 +685,12 @@ pub struct Variable { pub name: OwnedPrefixedName, } +impl Visitable for Variable { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_variable(&self.name); + } +} + impl Expression for Variable { fn evaluate<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>) -> Result, Error> { let name = resolve_prefixed_name(context, &self.name)?; @@ -625,6 +722,11 @@ mod test { #[derive(Debug)] struct FailExpression; + impl Visitable for FailExpression { + fn visit(&self, _visitor: &mut dyn Visitor) { + panic!("Should never be called"); + } + } impl Expression for FailExpression { fn evaluate<'c, 'd>(&self, _: &context::Evaluation<'c, 'd>) -> Result, Error> { panic!("Should never be called"); @@ -1048,10 +1150,16 @@ mod test { } } - #[derive(Debug)] + #[derive(Debug, Clone)] struct DummyNodeTest; impl NodeTest for DummyNodeTest { fn test(&self, _context: &context::Evaluation<'_, '_>, _result: &mut OrderedNodes<'_>) {} + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + } + impl Visitable for DummyNodeTest { + fn visit(&self, _visitor: &mut dyn Visitor) {} } #[test] diff --git a/src/lib.rs b/src/lib.rs index 11e5afeb..2d8a3a06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,6 +109,7 @@ use crate::parser::Parser; use crate::tokenizer::{TokenDeabbreviator, Tokenizer}; pub use crate::context::Context; +pub use crate::visitor::{Visitable, Visitor}; #[macro_use] pub mod macros; @@ -121,6 +122,7 @@ pub mod nodeset; mod parser; mod token; mod tokenizer; +pub mod visitor; // These belong in the the document @@ -374,6 +376,12 @@ impl XPath { } } +impl Visitable for XPath { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_xpath(&self.0); + } +} + /// The primary entrypoint to convert an XPath represented as a string /// to a structure that can be evaluated. pub struct Factory { diff --git a/src/node_test.rs b/src/node_test.rs index 4571274d..ed676e88 100644 --- a/src/node_test.rs +++ b/src/node_test.rs @@ -4,9 +4,12 @@ use sxd_document::QName; use crate::context; use crate::nodeset::{self, OrderedNodes}; +use crate::visitor::{Visitable, Visitor}; -pub trait NodeTest: fmt::Debug { +pub trait NodeTest: fmt::Debug + Visitable { fn test<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>, result: &mut OrderedNodes<'d>); + + fn clone_box(&self) -> Box; } impl NodeTest for Box @@ -16,6 +19,10 @@ where fn test<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>, result: &mut OrderedNodes<'d>) { (**self).test(context, result) } + + fn clone_box(&self) -> Box { + (**self).clone_box() + } } pub type SubNodeTest = Box; @@ -43,7 +50,7 @@ impl NameTest { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Attribute { name_test: NameTest, } @@ -62,9 +69,19 @@ impl NodeTest for Attribute { } } } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } } -#[derive(Debug)] +impl Visitable for Attribute { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_attribute(self.name_test.prefix.as_ref().map(|s| s.as_str()), &self.name_test.local_part); + } +} + +#[derive(Debug, Clone)] pub struct Namespace { name_test: NameTest, } @@ -83,9 +100,19 @@ impl NodeTest for Namespace { } } } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } } -#[derive(Debug)] +impl Visitable for Namespace { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_namespace(self.name_test.prefix.as_ref().map(|s| s.as_str()), &self.name_test.local_part); + } +} + +#[derive(Debug, Clone)] pub struct Element { name_test: NameTest, } @@ -104,20 +131,40 @@ impl NodeTest for Element { } } } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Visitable for Element { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_element(self.name_test.prefix.as_ref().map(|s| s.as_str()), &self.name_test.local_part); + } } #[allow(missing_copy_implementations)] -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Node; impl NodeTest for Node { fn test<'c, 'd>(&self, context: &context::Evaluation<'c, 'd>, result: &mut OrderedNodes<'d>) { result.add(context.node); } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Visitable for Node { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_node(); + } } #[allow(missing_copy_implementations)] -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Text; impl NodeTest for Text { @@ -126,10 +173,20 @@ impl NodeTest for Text { result.add(context.node); } } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Visitable for Text { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_text(); + } } #[allow(missing_copy_implementations)] -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Comment; impl NodeTest for Comment { @@ -138,9 +195,19 @@ impl NodeTest for Comment { result.add(context.node); } } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } } -#[derive(Debug)] +impl Visitable for Comment { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_comment(); + } +} + +#[derive(Debug, Clone)] pub struct ProcessingInstruction { target: Option, } @@ -161,6 +228,16 @@ impl NodeTest for ProcessingInstruction { } } } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +impl Visitable for ProcessingInstruction { + fn visit(&self, visitor: &mut dyn Visitor) { + visitor.visit_processing_instruction(self.target.as_ref().map(|s| s.as_str())); + } } #[cfg(test)] diff --git a/src/visitor.rs b/src/visitor.rs new file mode 100644 index 00000000..7ff81227 --- /dev/null +++ b/src/visitor.rs @@ -0,0 +1,209 @@ +//! Support for visiting XPaths. + +use crate::axis::Axis; +use crate::expression::{Expression, Step, StepTest, Predicate, SubExpression}; +use crate::{LiteralValue, OwnedPrefixedName}; + +pub trait Visitable { + fn visit(&self, visitor: &mut dyn Visitor); +} + +impl Visitable for Box +where + T: Visitable, +{ + fn visit(&self, visitor: &mut dyn Visitor) { + (**self).visit(visitor) + } +} + +pub trait Visitor { + fn visit_and(&mut self, left: &SubExpression, right: &SubExpression); + + fn visit_attribute(&mut self, prefix: Option<&str>, local_part: &str); + + fn visit_comment(&mut self); + + fn visit_context_node(&mut self); + + fn visit_element(&mut self, prefix: Option<&str>, local_part: &str); + + fn visit_equal(&mut self, left: &SubExpression, right: &SubExpression); + + fn visit_filter(&mut self, node_selector: &SubExpression, predicate: &Predicate); + + fn visit_function(&mut self, name: &OwnedPrefixedName, arguments: &[SubExpression]); + + fn visit_literal(&mut self, value: &LiteralValue); + + fn visit_math( + &mut self, + left: &SubExpression, + right: &SubExpression, + operation: &fn(f64, f64) -> f64, + ); + + fn visit_namespace(&mut self, prefix: Option<&str>, local_part: &str); + + fn visit_node(&mut self); + + fn visit_negation(&mut self, expression: &SubExpression); + + fn visit_not_equal(&mut self, left: &SubExpression, right: &SubExpression); + + fn visit_or(&mut self, left: &SubExpression, right: &SubExpression); + + fn visit_path(&mut self, start_point: &SubExpression, steps: &[Step]); + + fn visit_processing_instruction(&mut self, target: Option<&str>); + + fn visit_relational( + &mut self, + left: &SubExpression, + right: &SubExpression, + operation: &fn(f64, f64) -> bool, + ); + + fn visit_root_node(&mut self); + + fn visit_step(&mut self, axis: &Axis, node_test: &StepTest, predicates: &[Predicate]); + + fn visit_text(&mut self); + + fn visit_union(&mut self, left: &SubExpression, right: &SubExpression); + + fn visit_variable(&mut self, name: &OwnedPrefixedName); + + fn visit_xpath(&mut self, xpath: &std::boxed::Box<(dyn Expression + 'static)>); +} + +#[cfg(test)] +mod test { + use crate::{Factory, LiteralValue, OwnedPrefixedName}; + use crate::axis::Axis; + use crate::expression::{Expression, Predicate, Step, StepTest, SubExpression}; + use crate::node_test::{Element, NameTest}; + use crate::visitor::{Visitable, Visitor}; + + #[derive(Debug)] + enum Node { + XPath(Box), + Path(Box, Vec), + RootNode, + None, + Step(Axis, StepTest, Vec), + } + + impl Visitor for Node { + fn visit_and(&mut self, _left: &SubExpression, _right: &SubExpression) { } + + fn visit_attribute(&mut self, _prefix: Option<&str>, _local_part: &str) { } + + fn visit_comment(&mut self) { } + + fn visit_context_node(&mut self) { } + + fn visit_element(&mut self, _prefix: Option<&str>, _local_part: &str) { } + + fn visit_equal(&mut self, _left: &SubExpression, _right: &SubExpression) { } + + fn visit_filter(&mut self, _node_selector: &SubExpression, _predicate: &Predicate) { } + + fn visit_function(&mut self, _name: &OwnedPrefixedName, _arguments: &[SubExpression]) { } + + fn visit_literal(&mut self, _value: &LiteralValue) { } + + fn visit_math( + &mut self, + _left: &SubExpression, + _right: &SubExpression, + _operation: &fn(f64, f64) -> f64, + ) { } + + fn visit_namespace(&mut self, _prefix: Option<&str>, _local_part: &str) { } + + fn visit_node(&mut self) { } + + fn visit_negation(&mut self, _expression: &SubExpression) { } + + fn visit_not_equal(&mut self, _left: &SubExpression, _right: &SubExpression) { } + + fn visit_or(&mut self, _left: &SubExpression, _right: &SubExpression) { } + + fn visit_path(&mut self, start_point: &SubExpression, steps: &[Step]) { + let mut start_point_node = Node::None; + start_point.visit(&mut start_point_node); + + let step_nodes = steps.iter().map(|step| { + let mut step_node = Node::None; + step.visit(&mut step_node); + step_node + }).collect::>(); + + *self = Node::Path(Box::new(start_point_node), step_nodes); + } + + fn visit_processing_instruction(&mut self, _target: Option<&str>) { } + + fn visit_relational( + &mut self, + _left: &SubExpression, + _right: &SubExpression, + _operation: &fn(f64, f64) -> bool, + ) { } + + fn visit_root_node(&mut self) { + *self = Node::RootNode; + } + + fn visit_step(&mut self, axis: &Axis, node_test: &StepTest, predicates: &[Predicate]) { + let predicate_nodes = predicates.iter().map(|predicate| { + let mut predicate_node = Node::None; + predicate.expression.visit(&mut predicate_node); + predicate_node + }).collect::>(); + + *self = Node::Step(axis.clone(), node_test.clone_box(), predicate_nodes); + } + + fn visit_text(&mut self) { } + + fn visit_union(&mut self, _left: &SubExpression, _right: &SubExpression) { } + + fn visit_variable(&mut self, _name: &OwnedPrefixedName) { } + + fn visit_xpath(&mut self, xpath: &std::boxed::Box<(dyn Expression + 'static)>) { + let mut xpath_node = Node::None; + xpath.visit(&mut xpath_node); + + *self = Node::XPath(Box::new(xpath_node)); + } + } + + #[test] + fn visit() { + let xpath = Factory::new().build("/root").expect("Could not compile XPath"); + + let mut node = Node::None; + xpath.visit(&mut node); + + assert_eq!( + format!("{:?}", node), + format!("{:?}", Node::XPath( + Box::new(Node::Path( + Box::new(Node::RootNode), + vec![ + Node::Step( + Axis::Child, + Box::new(Element::new(NameTest { + prefix: None, + local_part: "root".to_string() + })), + Vec::new() + ) + ], + )) + )), + ); + } +}