use crate::types::{ItemType, NameTest as RuntimeNameTest, SequenceType, XmlTypeCode};
use crate::xpath::arena::{AstArena, AstNodeId};
use crate::xpath::ast::{
AstNode, Axis, BinaryOpKind, FilterExprNode, ForBinding, ForNode, ItemTypeNode, KindTest,
NodeTest as AstNodeTest, OccurrenceIndicator, PathExprNode, PathStepNode, QuantifiedNode,
QuantifierKind, TypeExprKind, TypeExprNode, ValueNode,
};
use crate::xpath::axis_iterators::{
AncestorAxis, AttributeAxis, ChildAxis, DescendantNodeIterator, FollowingNodeIterator,
FollowingSiblingAxis, NamespaceAxis, ParentAxis, PrecedingNodeIterator, PrecedingSiblingAxis,
SelfAxis, SequentialAxisNodeIterator,
};
use crate::xpath::cast::{cast_to, castable, occurrence_allows_count, resolved_type_to_type_code};
use crate::xpath::context::{DynamicContext, XPathContext};
use crate::xpath::error::XPathError;
use crate::xpath::functions::{
atomize_to_single_opt, effective_boolean_value, effective_boolean_value_10, XPathValue,
};
use crate::xpath::iterator::{
DocumentOrderNodeIterator, VecNodeIterator, XmlItem, XmlNodeIterator,
};
use crate::xpath::node_ops::{following_node, get_root, preceding_node, same_node};
use crate::xpath::node_test::{matches_item_type_node, NodeTest};
use crate::xpath::operators::cast_to_qname_with_context;
use crate::xpath::operators::{
eval_binary, eval_numeric_binary_10, eval_range, eval_unary, general_eq_iter,
general_eq_iter_10, general_ge_iter, general_ge_iter_10, general_gt_iter, general_gt_iter_10,
general_le_iter, general_le_iter_10, general_lt_iter, general_lt_iter_10, general_ne_iter,
general_ne_iter_10,
};
use crate::xpath::sequence_ops::{except_nodes, intersect_nodes, union_nodes};
use crate::xpath::{DomNavigator, XPathMode};
pub fn eval_node<N: DomNavigator>(
arena: &AstArena,
id: AstNodeId,
ctx: &mut DynamicContext<'_, N>,
) -> Result<XPathValue<N>, XPathError> {
let node = arena.get(id);
match node {
AstNode::Expr(expr) => {
if expr.items.is_empty() {
return Ok(XPathValue::empty());
}
if expr.items.len() == 1 {
return eval_node(arena, expr.items[0], ctx);
}
if ctx.static_context.mode() == XPathMode::XPath10 {
return Err(XPathError::XPST0003 {
message: "Sequence expressions (comma operator) are not available in XPath 1.0"
.to_string(),
});
}
let mut results: Vec<XmlItem<N>> = Vec::new();
for item_id in &expr.items {
let value = eval_node(arena, *item_id, ctx)?;
results.extend(value.into_vec());
}
Ok(XPathValue::from_sequence(results))
}
AstNode::Value(value_node) => {
if ctx.static_context.mode() == XPathMode::XPath10 {
if matches!(value_node, ValueNode::Empty) {
return Err(XPathError::XPST0003 {
message: "Empty sequence () is not available in XPath 1.0".to_string(),
});
}
if matches!(value_node, ValueNode::Double(_)) {
return Err(XPathError::XPST0003 {
message: "Double literals (e.g. 1e10) are not available in XPath 1.0"
.to_string(),
});
}
}
eval_value(value_node, ctx.static_context.mode())
}
AstNode::ContextItem(_) => {
match &ctx.context_item {
Some(item) => Ok(XPathValue::from_item(item.clone())),
None => Err(XPathError::XPDY0002 {
message: "Context item is undefined".to_string(),
}),
}
}
AstNode::VarRef(var_ref) => {
let slot = var_ref
.slot
.ok_or_else(|| XPathError::Internal("Variable reference not bound".to_string()))?;
ctx.get_variable(slot)
.cloned()
.ok_or_else(|| XPathError::XPDY0002 {
message: format!("Variable ${} is not set", var_ref.local_name),
})
}
AstNode::If(if_node) => {
let test_value = eval_node(arena, if_node.test, ctx)?;
let condition = effective_boolean_value(&test_value)?;
if condition {
eval_node(arena, if_node.then_branch, ctx)
} else {
eval_node(arena, if_node.else_branch, ctx)
}
}
AstNode::FunctionCall(func_call) => {
let handle = func_call
.function_handle
.ok_or_else(|| XPathError::Internal("Function call not bound".to_string()))?;
let mut args: Vec<XPathValue<N>> = Vec::with_capacity(func_call.args.len());
for arg_id in &func_call.args {
args.push(eval_node(arena, *arg_id, ctx)?);
}
ctx.eval_function(handle, args)
}
AstNode::For(for_node) => eval_for_expression(arena, for_node, ctx),
AstNode::Quantified(quant_node) => eval_quantified_expression(arena, quant_node, ctx),
AstNode::PathExpr(path_expr) => eval_path_expr(arena, path_expr, ctx),
AstNode::FilterExpr(filter_expr) => eval_filter_expr(arena, filter_expr, ctx),
AstNode::Range(range) => {
let start_val = eval_node(arena, range.start, ctx)?;
let end_val = eval_node(arena, range.end, ctx)?;
let start_opt = atomize_to_single_opt(start_val)?;
let end_opt = atomize_to_single_opt(end_val)?;
match (start_opt, end_opt) {
(None, _) | (_, None) => Ok(XPathValue::empty()),
(Some(start), Some(end)) => {
let values = eval_range(&start, &end)?;
let items: Vec<XmlItem<N>> = values.into_iter().map(XmlItem::Atomic).collect();
Ok(XPathValue::from_sequence(items))
}
}
}
AstNode::UnaryOp(unary_op) => {
let operand_val = eval_node(arena, unary_op.operand, ctx)?;
let opt = atomize_to_single_opt(operand_val)?;
match opt {
None => Ok(XPathValue::empty()),
Some(operand) => {
let result = eval_unary(unary_op.kind, &operand)?;
Ok(XPathValue::from_atomic(result))
}
}
}
AstNode::BinaryOp(bin_op) => {
match bin_op.kind {
BinaryOpKind::And => {
let left_val = eval_node(arena, bin_op.left, ctx)?;
let left_bool = if ctx.static_context.mode() == XPathMode::XPath10 {
effective_boolean_value_10(&left_val)?
} else {
effective_boolean_value(&left_val)?
};
if !left_bool {
return Ok(XPathValue::boolean(false));
}
let right_val = eval_node(arena, bin_op.right, ctx)?;
let right_bool = if ctx.static_context.mode() == XPathMode::XPath10 {
effective_boolean_value_10(&right_val)?
} else {
effective_boolean_value(&right_val)?
};
Ok(XPathValue::boolean(right_bool))
}
BinaryOpKind::Or => {
let left_val = eval_node(arena, bin_op.left, ctx)?;
let left_bool = if ctx.static_context.mode() == XPathMode::XPath10 {
effective_boolean_value_10(&left_val)?
} else {
effective_boolean_value(&left_val)?
};
if left_bool {
return Ok(XPathValue::boolean(true));
}
let right_val = eval_node(arena, bin_op.right, ctx)?;
let right_bool = if ctx.static_context.mode() == XPathMode::XPath10 {
effective_boolean_value_10(&right_val)?
} else {
effective_boolean_value(&right_val)?
};
Ok(XPathValue::boolean(right_bool))
}
BinaryOpKind::Add
| BinaryOpKind::Sub
| BinaryOpKind::Mul
| BinaryOpKind::Div
| BinaryOpKind::IDiv
| BinaryOpKind::Mod
| BinaryOpKind::ValueEq
| BinaryOpKind::ValueNe
| BinaryOpKind::ValueLt
| BinaryOpKind::ValueLe
| BinaryOpKind::ValueGt
| BinaryOpKind::ValueGe => {
let left_val = eval_node(arena, bin_op.left, ctx)?;
let right_val = eval_node(arena, bin_op.right, ctx)?;
let left_opt = atomize_to_single_opt(left_val)?;
let right_opt = atomize_to_single_opt(right_val)?;
match (left_opt, right_opt) {
(None, _) | (_, None) => Ok(XPathValue::empty()),
(Some(left), Some(right)) => {
let is_arithmetic = matches!(
bin_op.kind,
BinaryOpKind::Add
| BinaryOpKind::Sub
| BinaryOpKind::Mul
| BinaryOpKind::Div
| BinaryOpKind::Mod
);
let result = if is_arithmetic
&& ctx.static_context.mode() == XPathMode::XPath10
{
eval_numeric_binary_10(bin_op.kind, &left, &right)?
} else {
eval_binary(bin_op.kind, &left, &right)?
};
Ok(XPathValue::from_atomic(result))
}
}
}
BinaryOpKind::GeneralEq
| BinaryOpKind::GeneralNe
| BinaryOpKind::GeneralLt
| BinaryOpKind::GeneralLe
| BinaryOpKind::GeneralGt
| BinaryOpKind::GeneralGe => {
let left_val = eval_node(arena, bin_op.left, ctx)?;
let right_val = eval_node(arena, bin_op.right, ctx)?;
if ctx.static_context.mode() == XPathMode::XPath10 {
let left_is_bool = is_boolean_value(&left_val);
let right_is_bool = is_boolean_value(&right_val);
let left_has_nodes = has_nodes_or_empty(&left_val);
let right_has_nodes = has_nodes_or_empty(&right_val);
if (left_is_bool && right_has_nodes) || (right_is_bool && left_has_nodes) {
let l = effective_boolean_value_10(&left_val)?;
let r = effective_boolean_value_10(&right_val)?;
let result = match bin_op.kind {
BinaryOpKind::GeneralEq => l == r,
BinaryOpKind::GeneralNe => l != r,
BinaryOpKind::GeneralLt
| BinaryOpKind::GeneralLe
| BinaryOpKind::GeneralGt
| BinaryOpKind::GeneralGe => {
let ln = if l { 1.0_f64 } else { 0.0 };
let rn = if r { 1.0_f64 } else { 0.0 };
match bin_op.kind {
BinaryOpKind::GeneralLt => ln < rn,
BinaryOpKind::GeneralLe => ln <= rn,
BinaryOpKind::GeneralGt => ln > rn,
BinaryOpKind::GeneralGe => ln >= rn,
_ => unreachable!(),
}
}
_ => unreachable!(),
};
return Ok(XPathValue::boolean(result));
}
}
let left_iter = VecNodeIterator::new(left_val.into_vec());
let right_iter = VecNodeIterator::new(right_val.into_vec());
let result = if ctx.static_context.mode() == XPathMode::XPath10 {
match bin_op.kind {
BinaryOpKind::GeneralEq => general_eq_iter_10(&left_iter, &right_iter)?,
BinaryOpKind::GeneralNe => general_ne_iter_10(&left_iter, &right_iter)?,
BinaryOpKind::GeneralLt => general_lt_iter_10(&left_iter, &right_iter)?,
BinaryOpKind::GeneralLe => general_le_iter_10(&left_iter, &right_iter)?,
BinaryOpKind::GeneralGt => general_gt_iter_10(&left_iter, &right_iter)?,
BinaryOpKind::GeneralGe => general_ge_iter_10(&left_iter, &right_iter)?,
_ => unreachable!(),
}
} else {
match bin_op.kind {
BinaryOpKind::GeneralEq => {
general_eq_iter(ctx.static_context, &left_iter, &right_iter)?
}
BinaryOpKind::GeneralNe => {
general_ne_iter(ctx.static_context, &left_iter, &right_iter)?
}
BinaryOpKind::GeneralLt => {
general_lt_iter(ctx.static_context, &left_iter, &right_iter)?
}
BinaryOpKind::GeneralLe => {
general_le_iter(ctx.static_context, &left_iter, &right_iter)?
}
BinaryOpKind::GeneralGt => {
general_gt_iter(ctx.static_context, &left_iter, &right_iter)?
}
BinaryOpKind::GeneralGe => {
general_ge_iter(ctx.static_context, &left_iter, &right_iter)?
}
_ => unreachable!(),
}
};
Ok(XPathValue::boolean(result))
}
BinaryOpKind::Is | BinaryOpKind::Before | BinaryOpKind::After => {
let left_val = eval_node(arena, bin_op.left, ctx)?;
let right_val = eval_node(arena, bin_op.right, ctx)?;
let left_node = extract_single_node(left_val)?;
let right_node = extract_single_node(right_val)?;
match (left_node, right_node) {
(Some(left), Some(right)) => {
let result = match bin_op.kind {
BinaryOpKind::Is => same_node(&left, &right),
BinaryOpKind::Before => preceding_node(&left, &right),
BinaryOpKind::After => following_node(&left, &right),
_ => unreachable!(),
};
Ok(XPathValue::boolean(result))
}
_ => Ok(XPathValue::empty()),
}
}
BinaryOpKind::Union | BinaryOpKind::Intersect | BinaryOpKind::Except => {
let left_val = eval_node(arena, bin_op.left, ctx)?;
let right_val = eval_node(arena, bin_op.right, ctx)?;
let left_vec = left_val.into_vec();
let right_vec = right_val.into_vec();
let result = match bin_op.kind {
BinaryOpKind::Union => union_nodes(left_vec, right_vec)?,
BinaryOpKind::Intersect => intersect_nodes(left_vec, right_vec)?,
BinaryOpKind::Except => except_nodes(left_vec, right_vec)?,
_ => unreachable!(),
};
Ok(XPathValue::from_sequence(result))
}
}
}
AstNode::PathStep(_) => {
Err(XPathError::Internal(
"PathStep should not be evaluated directly".to_string(),
))
}
AstNode::TypeExpr(type_expr) => eval_type_expr(arena, type_expr, ctx),
}
}
fn eval_type_expr<N: DomNavigator>(
arena: &AstArena,
type_expr: &TypeExprNode,
ctx: &mut DynamicContext<'_, N>,
) -> Result<XPathValue<N>, XPathError> {
let operand = eval_node(arena, type_expr.operand, ctx)?;
match type_expr.kind {
TypeExprKind::InstanceOf => eval_instance_of(operand, type_expr, ctx),
TypeExprKind::TreatAs => eval_treat_as(operand, type_expr, ctx),
TypeExprKind::CastAs => eval_cast_as(operand, type_expr, ctx),
TypeExprKind::CastableAs => eval_castable_as(operand, type_expr, ctx),
}
}
fn eval_instance_of<N: DomNavigator>(
operand: XPathValue<N>,
type_expr: &TypeExprNode,
ctx: &DynamicContext<'_, N>,
) -> Result<XPathValue<N>, XPathError> {
let items = operand.into_vec();
let count = items.len();
if type_expr.target_type.item_type.is_none() {
return Ok(XPathValue::boolean(count == 0));
}
if !occurrence_allows_count(type_expr.target_type.occurrence, count) {
return Ok(XPathValue::boolean(false));
}
let item_type = type_expr.target_type.item_type.as_ref().unwrap();
for item in &items {
if !matches_item_type_node(
item,
item_type,
type_expr.resolved_atomic_type.as_ref(),
ctx.static_context,
) {
return Ok(XPathValue::boolean(false));
}
}
Ok(XPathValue::boolean(true))
}
fn eval_treat_as<N: DomNavigator>(
operand: XPathValue<N>,
type_expr: &TypeExprNode,
ctx: &DynamicContext<'_, N>,
) -> Result<XPathValue<N>, XPathError> {
let items = operand.into_vec();
let count = items.len();
if !occurrence_allows_count(type_expr.target_type.occurrence, count) {
return Err(XPathError::XPTY0004 {
expected: format_sequence_type(&type_expr.target_type, ctx),
found: format!("sequence of {} items", count),
});
}
let item_type = match &type_expr.target_type.item_type {
None => {
if count == 0 {
return Ok(XPathValue::empty());
} else {
return Err(XPathError::XPTY0004 {
expected: "empty-sequence()".to_string(),
found: format!("sequence of {} items", count),
});
}
}
Some(it) => it,
};
for item in &items {
if !matches_item_type_node(
item,
item_type,
type_expr.resolved_atomic_type.as_ref(),
ctx.static_context,
) {
return Err(XPathError::XPTY0004 {
expected: format_sequence_type(&type_expr.target_type, ctx),
found: format_item_type(item),
});
}
}
Ok(XPathValue::from_sequence(items))
}
fn eval_cast_as<N: DomNavigator>(
operand: XPathValue<N>,
type_expr: &TypeExprNode,
ctx: &DynamicContext<'_, N>,
) -> Result<XPathValue<N>, XPathError> {
let item_type =
type_expr
.target_type
.item_type
.as_ref()
.ok_or_else(|| XPathError::XPTY0004 {
expected: "atomic type".to_string(),
found: "empty-sequence()".to_string(),
})?;
if !matches!(item_type, ItemTypeNode::Atomic(_)) {
return Err(XPathError::XPTY0004 {
expected: "atomic type".to_string(),
found: "non-atomic type".to_string(),
});
}
let atomic_opt = atomize_to_single_opt(operand)?;
let allows_empty = matches!(
type_expr.target_type.occurrence,
OccurrenceIndicator::ZeroOrOne | OccurrenceIndicator::ZeroOrMore
);
match atomic_opt {
None => {
if allows_empty {
Ok(XPathValue::empty())
} else {
Err(XPathError::XPTY0004 {
expected: format_sequence_type(&type_expr.target_type, ctx),
found: "empty-sequence()".to_string(),
})
}
}
Some(value) => {
let qname = type_expr
.resolved_atomic_type
.as_ref()
.ok_or_else(|| XPathError::Internal("Cast target type not resolved".to_string()))?;
let target_type = resolved_type_to_type_code(qname, ctx.static_context.names)?;
let result = if matches!(target_type, XmlTypeCode::QName | XmlTypeCode::Notation) {
cast_to_qname_with_context(ctx.static_context, &value, target_type)?
} else {
cast_to(&value, target_type)?
};
Ok(XPathValue::from_atomic(result))
}
}
}
fn eval_castable_as<N: DomNavigator>(
operand: XPathValue<N>,
type_expr: &TypeExprNode,
ctx: &DynamicContext<'_, N>,
) -> Result<XPathValue<N>, XPathError> {
let item_type = type_expr.target_type.item_type.as_ref();
if !matches!(item_type, Some(ItemTypeNode::Atomic(_))) {
return Ok(XPathValue::boolean(false));
}
let atomic_opt = match atomize_to_single_opt(operand) {
Ok(opt) => opt,
Err(_) => return Ok(XPathValue::boolean(false)), };
let allows_empty = matches!(
type_expr.target_type.occurrence,
OccurrenceIndicator::ZeroOrOne | OccurrenceIndicator::ZeroOrMore
);
match atomic_opt {
None => {
Ok(XPathValue::boolean(allows_empty))
}
Some(value) => {
let qname = match type_expr.resolved_atomic_type.as_ref() {
Some(q) => q,
None => return Ok(XPathValue::boolean(false)),
};
let target_type = match resolved_type_to_type_code(qname, ctx.static_context.names) {
Ok(tc) => tc,
Err(_) => return Ok(XPathValue::boolean(false)),
};
let is_castable = if matches!(target_type, XmlTypeCode::QName | XmlTypeCode::Notation) {
cast_to_qname_with_context(ctx.static_context, &value, target_type).is_ok()
} else {
castable(&value, target_type)
};
Ok(XPathValue::boolean(is_castable))
}
}
}
fn format_sequence_type<N: DomNavigator>(
seq_type: &crate::xpath::ast::SequenceTypeNode,
_ctx: &DynamicContext<'_, N>,
) -> String {
let item_str = match &seq_type.item_type {
None => "empty-sequence()".to_string(),
Some(ItemTypeNode::Item) => "item()".to_string(),
Some(ItemTypeNode::Atomic(qname)) => {
if qname.prefix.is_empty() {
qname.local.clone()
} else {
format!("{}:{}", qname.prefix, qname.local)
}
}
Some(ItemTypeNode::Kind(kind)) => format_kind_test(kind),
};
let occ_str = match seq_type.occurrence {
OccurrenceIndicator::One => "",
OccurrenceIndicator::ZeroOrOne => "?",
OccurrenceIndicator::ZeroOrMore => "*",
OccurrenceIndicator::OneOrMore => "+",
};
format!("{}{}", item_str, occ_str)
}
fn format_kind_test(kind: &crate::xpath::ast::KindTest) -> String {
use crate::xpath::ast::KindTest;
match kind {
KindTest::AnyKind => "node()".to_string(),
KindTest::Text => "text()".to_string(),
KindTest::Comment => "comment()".to_string(),
KindTest::ProcessingInstruction(None) => "processing-instruction()".to_string(),
KindTest::ProcessingInstruction(Some(name)) => {
format!("processing-instruction('{}')", name)
}
KindTest::Document(None) => "document-node()".to_string(),
KindTest::Document(Some(inner)) => {
format!("document-node({})", format_kind_test(inner))
}
KindTest::Element(test) => {
if let Some(ref qname) = test.name {
if qname.prefix.is_empty() {
format!("element({})", qname.local)
} else {
format!("element({}:{})", qname.prefix, qname.local)
}
} else {
"element()".to_string()
}
}
KindTest::Attribute(test) => {
if let Some(ref qname) = test.name {
if qname.prefix.is_empty() {
format!("attribute({})", qname.local)
} else {
format!("attribute({}:{})", qname.prefix, qname.local)
}
} else {
"attribute()".to_string()
}
}
KindTest::SchemaElement(name) => format!("schema-element({})", name),
KindTest::SchemaAttribute(name) => format!("schema-attribute({})", name),
}
}
fn format_item_type<N: DomNavigator>(item: &XmlItem<N>) -> String {
match item {
XmlItem::Node(nav) => {
use crate::xpath::DomNodeType;
match nav.node_type() {
DomNodeType::Root => "document-node()".to_string(),
DomNodeType::Element => format!("element({})", nav.local_name()),
DomNodeType::Attribute => format!("attribute({})", nav.local_name()),
DomNodeType::Text
| DomNodeType::Whitespace
| DomNodeType::SignificantWhitespace => "text()".to_string(),
DomNodeType::Comment => "comment()".to_string(),
DomNodeType::ProcessingInstruction => "processing-instruction()".to_string(),
DomNodeType::Namespace => "namespace-node()".to_string(),
DomNodeType::All => "node()".to_string(),
}
}
XmlItem::Atomic(value) => {
format!("{:?}", value.type_code)
}
}
}
fn eval_path_expr<N: DomNavigator>(
arena: &AstArena,
path_expr: &PathExprNode,
ctx: &mut DynamicContext<'_, N>,
) -> Result<XPathValue<N>, XPathError> {
if path_expr.is_absolute && path_expr.steps.is_empty() {
let context_node = ctx.require_context_node()?;
let root = get_root(context_node);
return Ok(XPathValue::from_node(root));
}
let first_is_primary = path_expr.steps.first().is_some_and(|&step_id| {
matches!(
arena.get(step_id),
AstNode::FilterExpr(_)
| AstNode::FunctionCall(_)
| AstNode::Value(_)
| AstNode::Expr(_)
| AstNode::VarRef(_)
| AstNode::TypeExpr(_)
| AstNode::ContextItem(_)
)
});
let starting_nodes: Vec<N> = if path_expr.is_absolute {
let context_node = ctx.require_context_node()?;
vec![get_root(context_node)]
} else if first_is_primary {
Vec::new()
} else {
let context_node = ctx.require_context_node()?;
vec![context_node.clone()]
};
let needs_doc_order = path_needs_document_order(arena, path_expr);
let mut current_nodes: Vec<XmlItem<N>> =
starting_nodes.into_iter().map(XmlItem::Node).collect();
for (step_idx, &step_id) in path_expr.steps.iter().enumerate() {
let step_node = arena.get(step_id);
current_nodes = match step_node {
AstNode::PathStep(path_step) => {
eval_path_step(arena, path_step, current_nodes, ctx, step_idx == 0)?
}
AstNode::FilterExpr(filter_expr) => {
if step_idx == 0 {
let result = eval_filter_expr(arena, filter_expr, ctx)?;
result.into_vec()
} else {
let mut results = Vec::new();
for item in current_nodes {
let saved_context = ctx.context_item.take();
let saved_pos = ctx.context_position;
let saved_size = ctx.context_size;
ctx.context_item = Some(item);
ctx.context_position = 1;
ctx.context_size = 1;
let step_result = eval_filter_expr(arena, filter_expr, ctx)?;
results.extend(step_result.into_vec());
ctx.context_item = saved_context;
ctx.context_position = saved_pos;
ctx.context_size = saved_size;
}
results
}
}
_ => {
if step_idx == 0 && current_nodes.is_empty() {
let result = eval_node(arena, step_id, ctx)?;
result.into_vec()
} else {
let mut results = Vec::new();
for item in current_nodes {
let saved_context = ctx.context_item.take();
let saved_pos = ctx.context_position;
let saved_size = ctx.context_size;
ctx.context_item = Some(item);
ctx.context_position = 1;
ctx.context_size = 1;
let step_result = eval_node(arena, step_id, ctx)?;
results.extend(step_result.into_vec());
ctx.context_item = saved_context;
ctx.context_position = saved_pos;
ctx.context_size = saved_size;
}
results
}
}
};
if current_nodes.is_empty() {
return Ok(XPathValue::empty());
}
}
if needs_doc_order && !current_nodes.is_empty() {
let iter = VecNodeIterator::new(current_nodes);
let doc_order_iter = DocumentOrderNodeIterator::new(iter)?;
let mut doc_order_iter = doc_order_iter;
current_nodes = collect_iterator(&mut doc_order_iter)?;
}
Ok(XPathValue::from_sequence(current_nodes))
}
fn path_needs_document_order(arena: &AstArena, path_expr: &PathExprNode) -> bool {
let len = path_expr.steps.len();
for (idx, &step_id) in path_expr.steps.iter().enumerate() {
match arena.get(step_id) {
AstNode::PathStep(step) => {
if step.axis.is_reverse() {
return true;
}
if matches!(
step.axis,
Axis::Descendant | Axis::DescendantOrSelf | Axis::Following
) {
for s in (idx + 1)..len {
if let AstNode::PathStep(next_step) = arena.get(path_expr.steps[s]) {
if !matches!(next_step.axis, Axis::Attribute | Axis::Namespace) {
return true;
}
}
}
}
}
AstNode::FilterExpr(_) if idx > 0 => {
return true;
}
_ => {}
}
}
false
}
fn eval_path_step<N: DomNavigator>(
arena: &AstArena,
step: &PathStepNode,
input_nodes: Vec<XmlItem<N>>,
ctx: &mut DynamicContext<'_, N>,
_is_first_step: bool,
) -> Result<Vec<XmlItem<N>>, XPathError> {
let nodes: Vec<N> = input_nodes
.into_iter()
.map(|item| match item {
XmlItem::Node(n) => Ok(n),
XmlItem::Atomic(_) => Err(XPathError::XPTY0019),
})
.collect::<Result<Vec<_>, _>>()?;
if nodes.is_empty() {
return Ok(Vec::new());
}
let node_test = step_to_node_test(step, ctx.static_context);
let base_iter = VecNodeIterator::new(nodes.into_iter().map(XmlItem::Node).collect());
let xpath_ctx = ctx.static_context.clone();
let stepped_items = apply_axis_iterator(step.axis, node_test, xpath_ctx, base_iter)?;
if step.predicates.is_empty() {
Ok(stepped_items)
} else {
eval_predicates(arena, &step.predicates, ctx, stepped_items)
}
}
fn step_to_node_test(step: &PathStepNode, ctx: &XPathContext<'_>) -> Option<NodeTest> {
if let Some(ref resolved) = step.resolved_test {
return Some(NodeTest::Name(resolved.clone()));
}
match &step.test {
AstNodeTest::Name(name_test) => {
match (&name_test.prefix, &name_test.local_name) {
(None, None) => {
Some(NodeTest::Name(RuntimeNameTest::Wildcard))
}
(None, Some(local)) => {
let local_id = ctx.names.add(local);
Some(NodeTest::Name(RuntimeNameTest::NamespaceWildcard(local_id)))
}
(Some(prefix), None) => {
if let Some(ns_uri) = ctx.resolve_prefix(prefix) {
let ns_id = ctx.names.add(&ns_uri);
Some(NodeTest::Name(RuntimeNameTest::LocalWildcard(ns_id)))
} else {
None }
}
(Some(prefix), Some(local)) => {
let local_id = ctx.names.add(local);
let ns_uri = if prefix.is_empty() {
ctx.default_element_ns
} else {
ctx.resolve_prefix(prefix).map(|s| ctx.names.add(&s))
};
let qname = crate::namespace::qname::QualifiedName::new(ns_uri, local_id, None);
Some(NodeTest::Name(RuntimeNameTest::QName(qname)))
}
}
}
AstNodeTest::Kind(kind_test) => {
let seq_type = kind_test_to_sequence_type(kind_test);
Some(NodeTest::Type(seq_type))
}
}
}
fn kind_test_to_sequence_type(kind: &KindTest) -> SequenceType {
match kind {
KindTest::AnyKind => SequenceType::node(),
KindTest::Text => SequenceType::one(ItemType::Text),
KindTest::Comment => SequenceType::one(ItemType::Comment),
KindTest::ProcessingInstruction(target) => {
SequenceType::one(ItemType::ProcessingInstruction(target.clone()))
}
KindTest::Document(inner) => {
let inner_type = inner.as_ref().map(|k| Box::new(kind_test_to_item_type(k)));
SequenceType::one(ItemType::Document(inner_type))
}
KindTest::Element(_) => {
SequenceType::one(ItemType::Element(None, None))
}
KindTest::Attribute(_) => {
SequenceType::one(ItemType::Attribute(None, None))
}
KindTest::SchemaElement(_) | KindTest::SchemaAttribute(_) => {
SequenceType::node()
}
}
}
fn kind_test_to_item_type(kind: &KindTest) -> ItemType {
match kind {
KindTest::AnyKind => ItemType::AnyNode,
KindTest::Text => ItemType::Text,
KindTest::Comment => ItemType::Comment,
KindTest::ProcessingInstruction(target) => ItemType::ProcessingInstruction(target.clone()),
KindTest::Document(inner) => {
let inner_type = inner.as_ref().map(|k| Box::new(kind_test_to_item_type(k)));
ItemType::Document(inner_type)
}
KindTest::Element(_) => ItemType::Element(None, None),
KindTest::Attribute(_) => ItemType::Attribute(None, None),
KindTest::SchemaElement(_) | KindTest::SchemaAttribute(_) => ItemType::AnyNode,
}
}
fn apply_axis_iterator<N: DomNavigator>(
axis: Axis,
node_test: Option<NodeTest>,
ctx: XPathContext<'_>,
base_iter: VecNodeIterator<N>,
) -> Result<Vec<XmlItem<N>>, XPathError> {
match axis {
Axis::Child => {
let mut iter =
SequentialAxisNodeIterator::new(ctx, node_test, false, base_iter, ChildAxis);
collect_iterator(&mut iter)
}
Axis::Descendant => {
let mut iter = DescendantNodeIterator::new(ctx, node_test, false, base_iter);
collect_iterator(&mut iter)
}
Axis::DescendantOrSelf => {
let mut iter = DescendantNodeIterator::new(ctx, node_test, true, base_iter);
collect_iterator(&mut iter)
}
Axis::Attribute => {
let mut iter =
SequentialAxisNodeIterator::new(ctx, node_test, false, base_iter, AttributeAxis);
collect_iterator(&mut iter)
}
Axis::SelfAxis => {
let mut iter =
SequentialAxisNodeIterator::new(ctx, node_test, false, base_iter, SelfAxis);
collect_iterator(&mut iter)
}
Axis::Parent => {
let mut iter =
SequentialAxisNodeIterator::new(ctx, node_test, false, base_iter, ParentAxis);
collect_iterator(&mut iter)
}
Axis::Ancestor => {
let mut iter =
SequentialAxisNodeIterator::new(ctx, node_test, false, base_iter, AncestorAxis);
collect_iterator(&mut iter)
}
Axis::AncestorOrSelf => {
let mut iter =
SequentialAxisNodeIterator::new(ctx, node_test, true, base_iter, AncestorAxis);
collect_iterator(&mut iter)
}
Axis::FollowingSibling => {
let mut iter = SequentialAxisNodeIterator::new(
ctx,
node_test,
false,
base_iter,
FollowingSiblingAxis,
);
collect_iterator(&mut iter)
}
Axis::PrecedingSibling => {
let mut iter = SequentialAxisNodeIterator::new(
ctx,
node_test,
false,
base_iter,
PrecedingSiblingAxis,
);
collect_iterator(&mut iter)
}
Axis::Following => {
let mut iter = FollowingNodeIterator::new(ctx, node_test, base_iter);
collect_iterator(&mut iter)
}
Axis::Preceding => {
let mut iter = PrecedingNodeIterator::new(ctx, node_test, base_iter);
collect_iterator(&mut iter)
}
Axis::Namespace => {
let mut iter = SequentialAxisNodeIterator::new(
ctx,
node_test,
false,
base_iter,
NamespaceAxis::default(),
);
collect_iterator(&mut iter)
}
}
}
fn collect_iterator<I: XmlNodeIterator>(
iter: &mut I,
) -> Result<Vec<XmlItem<I::Navigator>>, XPathError> {
let mut results = Vec::new();
while iter.move_next()? {
if let Some(item_ref) = iter.current() {
let item = match item_ref {
crate::xpath::iterator::XmlItemRef::Node(n) => XmlItem::Node(n.clone()),
crate::xpath::iterator::XmlItemRef::Atomic(v) => XmlItem::Atomic(v.clone()),
};
results.push(item);
}
}
Ok(results)
}
fn eval_filter_expr<N: DomNavigator>(
arena: &AstArena,
filter_expr: &FilterExprNode,
ctx: &mut DynamicContext<'_, N>,
) -> Result<XPathValue<N>, XPathError> {
let base_value = eval_node(arena, filter_expr.base, ctx)?;
if filter_expr.predicates.is_empty() {
return Ok(base_value);
}
let items = base_value.into_vec();
let filtered = eval_predicates(arena, &filter_expr.predicates, ctx, items)?;
Ok(XPathValue::from_sequence(filtered))
}
fn eval_predicates<N: DomNavigator>(
arena: &AstArena,
predicates: &[AstNodeId],
ctx: &mut DynamicContext<'_, N>,
mut items: Vec<XmlItem<N>>,
) -> Result<Vec<XmlItem<N>>, XPathError> {
for &pred_id in predicates {
if items.is_empty() {
break;
}
let size = items.len();
let mut filtered = Vec::new();
for (idx, item) in items.into_iter().enumerate() {
let position = idx + 1;
let saved_item = ctx.context_item.take();
let saved_pos = ctx.context_position;
let saved_size = ctx.context_size;
ctx.context_item = Some(item.clone());
ctx.context_position = position;
ctx.context_size = size;
let pred_result = eval_node(arena, pred_id, ctx)?;
ctx.context_item = saved_item;
ctx.context_position = saved_pos;
ctx.context_size = saved_size;
let is_10 = ctx.static_context.mode() == XPathMode::XPath10;
let include = match &pred_result {
XPathValue::Item(XmlItem::Atomic(value)) if value.type_code.is_numeric() => {
let num = crate::xpath::atomize::to_number(value);
if num.is_nan() {
false
} else {
(position as f64) == num
}
}
_ => {
if is_10 {
effective_boolean_value_10(&pred_result)?
} else {
effective_boolean_value(&pred_result)?
}
}
};
if include {
filtered.push(item);
}
}
items = filtered;
}
Ok(items)
}
use std::ops::ControlFlow;
fn eval_for_expression<N: DomNavigator>(
arena: &AstArena,
for_node: &ForNode,
ctx: &mut DynamicContext<'_, N>,
) -> Result<XPathValue<N>, XPathError> {
let mut results: Vec<XmlItem<N>> = Vec::new();
match eval_for_bindings(
arena,
&for_node.bindings,
0,
for_node.return_expr,
ctx,
&mut |result| match result {
Ok(value) => {
results.extend(value.into_vec());
ControlFlow::Continue(())
}
Err(e) => ControlFlow::Break(e),
},
) {
ControlFlow::Continue(()) => {}
ControlFlow::Break(e) => return Err(e),
}
Ok(XPathValue::from_sequence(results))
}
fn eval_for_bindings<N: DomNavigator, B>(
arena: &AstArena,
bindings: &[ForBinding],
binding_index: usize,
body_id: AstNodeId,
ctx: &mut DynamicContext<'_, N>,
collector: &mut impl FnMut(Result<XPathValue<N>, XPathError>) -> ControlFlow<B>,
) -> ControlFlow<B> {
if binding_index >= bindings.len() {
let result = eval_node(arena, body_id, ctx);
return collector(result);
}
let binding = &bindings[binding_index];
let slot = match binding.slot {
Some(s) => s,
None => {
return collector(Err(XPathError::Internal(
"For binding slot not assigned".to_string(),
)))
}
};
let seq_value = match eval_node(arena, binding.in_expr, ctx) {
Ok(v) => v,
Err(e) => return collector(Err(e)),
};
let items = seq_value.into_vec();
if items.is_empty() {
return ControlFlow::Continue(());
}
for item in items {
ctx.set_variable(slot, XPathValue::from_item(item));
if let cf @ ControlFlow::Break(_) =
eval_for_bindings(arena, bindings, binding_index + 1, body_id, ctx, collector)
{
return cf;
}
}
ControlFlow::Continue(())
}
enum QuantifiedExit {
ShortCircuit,
Error(XPathError),
}
fn eval_quantified_expression<N: DomNavigator>(
arena: &AstArena,
quant_node: &QuantifiedNode,
ctx: &mut DynamicContext<'_, N>,
) -> Result<XPathValue<N>, XPathError> {
let mut had_any_iteration = false;
let mut found_some = false;
let mut all_satisfied = true;
match eval_for_bindings(
arena,
&quant_node.bindings,
0,
quant_node.satisfies,
ctx,
&mut |result| {
had_any_iteration = true;
match result {
Ok(value) => match effective_boolean_value(&value) {
Ok(satisfied) => {
match quant_node.kind {
QuantifierKind::Some => {
if satisfied {
found_some = true;
return ControlFlow::Break(QuantifiedExit::ShortCircuit);
}
}
QuantifierKind::Every => {
if !satisfied {
all_satisfied = false;
return ControlFlow::Break(QuantifiedExit::ShortCircuit);
}
}
}
ControlFlow::Continue(())
}
Err(e) => ControlFlow::Break(QuantifiedExit::Error(e)),
},
Err(e) => ControlFlow::Break(QuantifiedExit::Error(e)),
}
},
) {
ControlFlow::Continue(()) => {} ControlFlow::Break(QuantifiedExit::ShortCircuit) => {} ControlFlow::Break(QuantifiedExit::Error(e)) => return Err(e),
}
if !had_any_iteration {
return match quant_node.kind {
QuantifierKind::Some => Ok(XPathValue::boolean(false)),
QuantifierKind::Every => Ok(XPathValue::boolean(true)),
};
}
match quant_node.kind {
QuantifierKind::Some => Ok(XPathValue::boolean(found_some)),
QuantifierKind::Every => Ok(XPathValue::boolean(all_satisfied)),
}
}
fn eval_value<N: DomNavigator>(
value: &ValueNode,
mode: XPathMode,
) -> Result<XPathValue<N>, XPathError> {
match value {
ValueNode::Empty => Ok(XPathValue::empty()),
ValueNode::String(s) => Ok(XPathValue::string(s.clone())),
ValueNode::Boolean(b) => Ok(XPathValue::boolean(*b)),
ValueNode::Integer(s) => {
let i: num_bigint::BigInt = s.parse().map_err(|_| XPathError::FORG0001 {
value: s.clone(),
target_type: "xs:integer".to_string(),
})?;
Ok(XPathValue::integer(i))
}
ValueNode::Decimal(s) => {
if mode == XPathMode::XPath10 {
let d: f64 = s.parse().unwrap_or(f64::NAN);
Ok(XPathValue::double(d))
} else {
let d: rust_decimal::Decimal = s.parse().map_err(|_| XPathError::FORG0001 {
value: s.clone(),
target_type: "xs:decimal".to_string(),
})?;
Ok(XPathValue::decimal(d))
}
}
ValueNode::Double(s) => {
let d: f64 = s.parse().unwrap_or(f64::NAN);
Ok(XPathValue::double(d))
}
ValueNode::Typed(xml_value) => Ok(XPathValue::from_atomic(xml_value.clone())),
}
}
fn extract_single_node<N: DomNavigator>(value: XPathValue<N>) -> Result<Option<N>, XPathError> {
match value {
XPathValue::Empty => Ok(None),
XPathValue::Item(XmlItem::Node(node)) => Ok(Some(node)),
XPathValue::Item(XmlItem::Atomic(_)) => Err(XPathError::XPTY0004 {
expected: "node()".to_string(),
found: "atomic value".to_string(),
}),
XPathValue::Sequence(items) => {
if items.len() == 1 {
match items.into_iter().next().unwrap() {
XmlItem::Node(node) => Ok(Some(node)),
XmlItem::Atomic(_) => Err(XPathError::XPTY0004 {
expected: "node()".to_string(),
found: "atomic value".to_string(),
}),
}
} else if items.is_empty() {
Ok(None)
} else {
Err(XPathError::more_than_one_item())
}
}
}
}
fn is_boolean_value<N: DomNavigator>(val: &XPathValue<N>) -> bool {
matches!(val, XPathValue::Item(XmlItem::Atomic(v)) if v.type_code == crate::types::XmlTypeCode::Boolean)
}
fn has_nodes_or_empty<N: DomNavigator>(val: &XPathValue<N>) -> bool {
match val {
XPathValue::Empty => true,
XPathValue::Item(XmlItem::Node(_)) => true,
XPathValue::Sequence(items) => items.iter().any(|i| matches!(i, XmlItem::Node(_))),
_ => false,
}
}
#[cfg(test)]
#[path = "eval_tests.rs"]
mod eval_tests;