use xot::{ValueType, Xot};
use xee_xpath_ast::ast;
use crate::sequence;
use super::kind_test::kind_test;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Step {
pub axis: ast::Axis,
pub node_test: ast::NodeTest,
}
pub(crate) fn resolve_step(step: &Step, node: xot::Node, xot: &Xot) -> sequence::Sequence {
let mut new_items = Vec::new();
for axis_node in node_take_axis(&step.axis, xot, node) {
if node_test(&step.node_test, &step.axis, xot, axis_node) {
new_items.push(sequence::Item::Node(axis_node));
}
}
new_items.into()
}
fn convert_axis(axis: &ast::Axis) -> xot::Axis {
match axis {
ast::Axis::Child => xot::Axis::Child,
ast::Axis::Descendant => xot::Axis::Descendant,
ast::Axis::Parent => xot::Axis::Parent,
ast::Axis::Ancestor => xot::Axis::Ancestor,
ast::Axis::FollowingSibling => xot::Axis::FollowingSibling,
ast::Axis::PrecedingSibling => xot::Axis::PrecedingSibling,
ast::Axis::Following => xot::Axis::Following,
ast::Axis::Preceding => xot::Axis::Preceding,
ast::Axis::DescendantOrSelf => xot::Axis::DescendantOrSelf,
ast::Axis::AncestorOrSelf => xot::Axis::AncestorOrSelf,
ast::Axis::Self_ => xot::Axis::Self_,
ast::Axis::Attribute => xot::Axis::Attribute,
ast::Axis::Namespace => unreachable!("Namespace axis should be forbidden at compile time"),
}
}
fn node_take_axis<'a>(
axis: &ast::Axis,
xot: &'a Xot,
node: xot::Node,
) -> Box<dyn Iterator<Item = xot::Node> + 'a> {
let axis = convert_axis(axis);
xot.axis(axis, node)
}
fn node_test(node_test: &ast::NodeTest, axis: &ast::Axis, xot: &Xot, node: xot::Node) -> bool {
match node_test {
ast::NodeTest::KindTest(kt) => kind_test(kt, xot, node),
ast::NodeTest::NameTest(name_test) => {
if xot.value_type(node) != principal_node_kind(axis) {
return false;
}
match name_test {
ast::NameTest::Name(name) => {
let name = name.value.maybe_to_ref(xot);
if let Some(name) = name {
let name_id = name.name_id();
match xot.value(node) {
xot::Value::Element(element) => element.name() == name_id,
xot::Value::Attribute(attribute) => attribute.name() == name_id,
_ => false,
}
} else {
false
}
}
ast::NameTest::Star => true,
ast::NameTest::LocalName(local_name) => match xot.value(node) {
xot::Value::Element(element) => {
let name_id = element.name();
let (name_str, _) = xot.name_ns_str(name_id);
name_str == local_name
}
xot::Value::Attribute(attribute) => {
xot.local_name_str(attribute.name()) == local_name
}
_ => false,
},
ast::NameTest::Namespace(uri) => match xot.value(node) {
xot::Value::Element(element) => {
let name_id = element.name();
let namespace_str = xot.uri_str(name_id);
namespace_str == uri
}
xot::Value::Attribute(attribute) => {
let namespace_str = xot.uri_str(attribute.name());
namespace_str == uri
}
_ => false,
},
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PrincipalNodeKind {
Element,
Attribute,
Namespace,
}
fn principal_node_kind(axis: &ast::Axis) -> ValueType {
match axis {
ast::Axis::Attribute => ValueType::Attribute,
ast::Axis::Namespace => ValueType::Namespace,
_ => ValueType::Element,
}
}
#[cfg(test)]
mod tests {
use xee_xpath_ast::{ast, WithSpan};
use super::*;
fn xot_nodes_to_value(node: &[xot::Node]) -> sequence::Sequence {
node.iter()
.map(|&node| sequence::Item::Node(node))
.collect::<Vec<_>>()
.into()
}
#[test]
fn test_child_axis_star() -> Result<(), xot::Error> {
let mut xot = Xot::new();
let doc = xot.parse(r#"<root><a/><b/></root>"#).unwrap();
let doc_el = xot.document_element(doc)?;
let a = xot.first_child(doc_el).unwrap();
let b = xot.next_sibling(a).unwrap();
let step = Step {
axis: ast::Axis::Child,
node_test: ast::NodeTest::NameTest(ast::NameTest::Star),
};
let value = resolve_step(&step, doc_el, &xot);
assert_eq!(value, xot_nodes_to_value(&[a, b]));
Ok(())
}
#[test]
fn test_child_axis_name() -> Result<(), xot::Error> {
let mut xot = Xot::new();
let doc = xot.parse(r#"<root><a/><b/></root>"#).unwrap();
let doc_el = xot.document_element(doc)?;
let a = xot.first_child(doc_el).unwrap();
let step = Step {
axis: ast::Axis::Child,
node_test: ast::NodeTest::NameTest(ast::NameTest::Name(
ast::Name::name("a").with_empty_span(),
)),
};
let value = resolve_step(&step, doc_el, &xot);
assert_eq!(value, xot_nodes_to_value(&[a]));
Ok(())
}
}