use crate::namespace::qname::QualifiedName;
use crate::namespace::table::XS_NAMESPACE;
use crate::types::{NameTest as ResolvedNameTest, XmlTypeCode};
use crate::xpath::arena::{AstArena, AstNodeId};
use crate::xpath::ast::{
AstNode, FunctionCallNode, ItemTypeNode, NameTest, NodeTest, OccurrenceIndicator, QName,
SequenceTypeNode, TypeExprKind, TypeExprNode,
};
use crate::xpath::context::{NameBinder, XPathContext};
use crate::xpath::error::XPathError;
use crate::xpath::XPathMode;
pub fn bind_node(
arena: &mut AstArena,
id: AstNodeId,
ctx: &XPathContext<'_>,
binder: &mut NameBinder,
) -> Result<(), XPathError> {
let node = arena.get(id).clone();
match node {
AstNode::Expr(expr) => {
for item_id in &expr.items {
bind_node(arena, *item_id, ctx, binder)?;
}
}
AstNode::Value(_) => {
}
AstNode::ContextItem(_) => {
}
AstNode::VarRef(var_ref) => {
let name = resolve_var_qname(&var_ref.prefix, &var_ref.local_name, ctx)?;
let var = binder.resolve_with_names(&name, ctx.names)?;
if let AstNode::VarRef(ref mut node) = arena.get_mut(id) {
node.slot = Some(var.slot);
}
}
AstNode::If(if_node) => {
bind_node(arena, if_node.test, ctx, binder)?;
bind_node(arena, if_node.then_branch, ctx, binder)?;
bind_node(arena, if_node.else_branch, ctx, binder)?;
}
AstNode::For(for_node) => {
for binding_idx in 0..for_node.bindings.len() {
let binding = &for_node.bindings[binding_idx];
bind_node(arena, binding.in_expr, ctx, binder)?;
let name = resolve_var_qname(&binding.prefix, &binding.local_name, ctx)?;
let var = binder.push_var(name);
if let AstNode::For(ref mut node) = arena.get_mut(id) {
node.bindings[binding_idx].slot = Some(var.slot);
}
}
bind_node(arena, for_node.return_expr, ctx, binder)?;
for _ in &for_node.bindings {
binder.pop_var();
}
}
AstNode::Quantified(quant_node) => {
for binding_idx in 0..quant_node.bindings.len() {
let binding = &quant_node.bindings[binding_idx];
bind_node(arena, binding.in_expr, ctx, binder)?;
let name = resolve_var_qname(&binding.prefix, &binding.local_name, ctx)?;
let var = binder.push_var(name);
if let AstNode::Quantified(ref mut node) = arena.get_mut(id) {
node.bindings[binding_idx].slot = Some(var.slot);
}
}
bind_node(arena, quant_node.satisfies, ctx, binder)?;
for _ in &quant_node.bindings {
binder.pop_var();
}
}
AstNode::FunctionCall(func_call) => {
for arg_id in &func_call.args {
bind_node(arena, *arg_id, ctx, binder)?;
}
let namespace = if func_call.prefix.is_empty() {
ctx.default_function_namespace().to_string()
} else {
ctx.resolve_prefix(&func_call.prefix)
.ok_or_else(|| XPathError::undefined_prefix(&func_call.prefix))?
.to_string()
};
if let Some(type_expr) = try_bind_constructor_function(&func_call, &namespace, ctx)? {
*arena.get_mut(id) = AstNode::TypeExpr(type_expr);
return Ok(());
}
let arity = func_call.args.len();
let handle = ctx
.function_catalog()
.lookup(&namespace, &func_call.local_name, arity)
.ok_or_else(|| {
XPathError::function_not_found(&func_call.local_name, arity, &namespace)
})?;
if let AstNode::FunctionCall(ref mut node) = arena.get_mut(id) {
node.function_handle = Some(handle);
}
}
AstNode::PathExpr(path_expr) => {
for step_id in &path_expr.steps {
bind_node(arena, *step_id, ctx, binder)?;
}
}
AstNode::FilterExpr(filter_expr) => {
bind_node(arena, filter_expr.base, ctx, binder)?;
for pred_id in &filter_expr.predicates {
bind_node(arena, *pred_id, ctx, binder)?;
}
}
AstNode::Range(range_node) => {
bind_node(arena, range_node.start, ctx, binder)?;
bind_node(arena, range_node.end, ctx, binder)?;
}
AstNode::UnaryOp(unary_op) => {
bind_node(arena, unary_op.operand, ctx, binder)?;
}
AstNode::BinaryOp(binary_op) => {
bind_node(arena, binary_op.left, ctx, binder)?;
bind_node(arena, binary_op.right, ctx, binder)?;
}
AstNode::PathStep(path_step) => {
for pred_id in &path_step.predicates {
bind_node(arena, *pred_id, ctx, binder)?;
}
let axis = path_step.axis;
let is_attribute_axis = matches!(
axis,
super::ast::Axis::Attribute | super::ast::Axis::Namespace
);
let resolved = resolve_node_test_with_axis(&path_step.test, ctx, is_attribute_axis)?;
if let AstNode::PathStep(ref mut node) = arena.get_mut(id) {
node.resolved_test = resolved;
}
}
AstNode::TypeExpr(type_expr) => {
bind_node(arena, type_expr.operand, ctx, binder)?;
if let Some(ItemTypeNode::Atomic(ref qname)) = type_expr.target_type.item_type {
let resolved = resolve_atomic_type_qname(qname, ctx)?;
if let AstNode::TypeExpr(ref mut node) = arena.get_mut(id) {
node.resolved_atomic_type = Some(resolved);
}
}
}
}
Ok(())
}
fn resolve_var_qname(
prefix: &str,
local_name: &str,
ctx: &XPathContext<'_>,
) -> Result<QualifiedName, XPathError> {
let local_id = ctx.names.add(local_name);
if prefix.is_empty() {
Ok(QualifiedName::local(local_id))
} else {
let prefix_id = ctx.names.add(prefix);
let ns_id = ctx
.resolve_prefix_id(prefix_id)
.ok_or_else(|| XPathError::undefined_prefix(prefix))?;
Ok(QualifiedName::new(Some(ns_id), local_id, Some(prefix_id)))
}
}
fn resolve_node_test_with_axis(
test: &NodeTest,
ctx: &XPathContext<'_>,
is_attribute_axis: bool,
) -> Result<Option<ResolvedNameTest>, XPathError> {
match test {
NodeTest::Name(name_test) => {
let resolved = resolve_name_test_with_axis(name_test, ctx, is_attribute_axis)?;
Ok(Some(resolved))
}
NodeTest::Kind(_) => {
Ok(None)
}
}
}
fn resolve_name_test_with_axis(
name_test: &NameTest,
ctx: &XPathContext<'_>,
is_attribute_axis: bool,
) -> Result<ResolvedNameTest, XPathError> {
match (&name_test.prefix, &name_test.local_name) {
(None, None) => Ok(ResolvedNameTest::Wildcard),
(None, Some(local)) => {
let local_id = ctx.names.add(local);
Ok(ResolvedNameTest::NamespaceWildcard(local_id))
}
(Some(prefix), None) => {
if prefix.is_empty() {
if !is_attribute_axis {
if let Some(ns_id) = ctx.default_element_ns {
return Ok(ResolvedNameTest::LocalWildcard(ns_id));
}
}
let empty_ns = ctx.names.add("");
Ok(ResolvedNameTest::LocalWildcard(empty_ns))
} else {
let prefix_id = ctx.names.add(prefix);
let ns_id = ctx
.resolve_prefix_id(prefix_id)
.ok_or_else(|| XPathError::undefined_prefix(prefix))?;
Ok(ResolvedNameTest::LocalWildcard(ns_id))
}
}
(Some(prefix), Some(local)) => {
let local_id = ctx.names.add(local);
if prefix.is_empty() {
let ns_id = if is_attribute_axis {
None
} else {
ctx.default_element_ns
};
Ok(ResolvedNameTest::QName(QualifiedName::new(
ns_id, local_id, None,
)))
} else {
let prefix_id = ctx.names.add(prefix);
let ns_id = ctx
.resolve_prefix_id(prefix_id)
.ok_or_else(|| XPathError::undefined_prefix(prefix))?;
Ok(ResolvedNameTest::QName(QualifiedName::new(
Some(ns_id),
local_id,
Some(prefix_id),
)))
}
}
}
}
fn resolve_atomic_type_qname(
qname: &QName,
ctx: &XPathContext<'_>,
) -> Result<QualifiedName, XPathError> {
let local_id = ctx.names.add(&qname.local);
if qname.prefix.is_empty() {
let ns_id = ctx.default_element_ns;
Ok(QualifiedName::new(ns_id, local_id, None))
} else {
let prefix_id = ctx.names.add(&qname.prefix);
let ns_id = ctx
.resolve_prefix_id(prefix_id)
.ok_or_else(|| XPathError::undefined_prefix(&qname.prefix))?;
Ok(QualifiedName::new(Some(ns_id), local_id, Some(prefix_id)))
}
}
fn try_bind_constructor_function(
func_call: &FunctionCallNode,
namespace: &str,
ctx: &XPathContext<'_>,
) -> Result<Option<TypeExprNode>, XPathError> {
if ctx.mode() != XPathMode::XPath20 {
return Ok(None);
}
if namespace != XS_NAMESPACE || func_call.args.len() != 1 {
return Ok(None);
}
let type_code = match XmlTypeCode::from_local_name(&func_call.local_name) {
Some(tc) => tc,
None => return Ok(None),
};
if type_code == XmlTypeCode::Notation {
return Err(XPathError::unknown_type(&func_call.local_name));
}
if type_code.is_list() || matches!(type_code, XmlTypeCode::AnyType | XmlTypeCode::AnySimpleType)
{
return Ok(None);
}
let qname = QName {
prefix: func_call.prefix.clone(),
local: func_call.local_name.clone(),
};
let target_type = SequenceTypeNode::single(
ItemTypeNode::Atomic(qname.clone()),
OccurrenceIndicator::ZeroOrOne,
func_call.span,
);
let mut type_expr = TypeExprNode::new(
TypeExprKind::CastAs,
func_call.args[0],
target_type,
func_call.span,
);
let resolved = resolve_atomic_type_qname(&qname, ctx)?;
type_expr.resolved_atomic_type = Some(resolved);
Ok(Some(type_expr))
}
#[cfg(test)]
#[path = "bind_tests.rs"]
mod bind_tests;