Files
servo/components/script/xpath/eval.rs
Simon Wülker bdf630563c script: Don't try to evaluate xpath variable references (#39395)
Variables in xpath via the javascript bindings are a bit mysterious, as
there is no way that a variable can be specified. We currently panic
when encountering a variable, which is not good. Instead we now throw an
error.

We keep parsing the variables because the code is already there and it
seems realistic that their behaviour will be specified in the future.
I'm fine with removing them too if that is preferred.

Testing: This behaviour is unspecified and different browser produce
different results. There is no "correct" way to do this, but we should
not crash
Part of: https://github.com/servo/servo/issues/34527

---------

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
2025-09-20 12:36:54 +00:00

785 lines
30 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::fmt;
use html5ever::{LocalName, Namespace, Prefix, QualName, local_name, namespace_prefix, ns};
use script_bindings::script_runtime::CanGc;
use super::parser::{
AdditiveOp, Axis, EqualityOp, Expr, FilterExpr, KindTest, Literal, MultiplicativeOp, NodeTest,
NumericLiteral, PathExpr, PredicateExpr, PredicateListExpr, PrimaryExpr,
QName as ParserQualName, RelationalOp, StepExpr, UnaryOp,
};
use super::{EvaluationCtx, Value};
use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::domname::namespace_from_domstring;
use crate::dom::bindings::error::Error as JsError;
use crate::dom::bindings::inheritance::{Castable, CharacterDataTypeId, NodeTypeId};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::xmlname;
use crate::dom::element::Element;
use crate::dom::node::{Node, ShadowIncluding};
use crate::dom::processinginstruction::ProcessingInstruction;
use crate::xpath::context::PredicateCtx;
#[derive(Clone, Debug)]
pub(crate) enum Error {
NotANodeset,
InvalidPath,
UnknownFunction {
name: QualName,
},
/// It is not clear where variables used in XPath expression should come from.
/// Firefox throws "NS_ERROR_ILLEGAL_VALUE" when using them, chrome seems to return
/// an empty result. We also error out.
///
/// See <https://github.com/whatwg/dom/issues/67>
CannotUseVariables,
UnknownNamespace {
prefix: String,
},
InvalidQName {
qname: ParserQualName,
},
FunctionEvaluation {
fname: String,
},
Internal {
msg: String,
},
/// A JS exception that needs to be propagated to the caller.
JsException(JsError),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::NotANodeset => write!(f, "expression did not evaluate to a nodeset"),
Error::InvalidPath => write!(f, "invalid path expression"),
Error::UnknownFunction { name } => write!(f, "unknown function {:?}", name),
Error::CannotUseVariables => write!(f, "cannot use variables"),
Error::UnknownNamespace { prefix } => {
write!(f, "unknown namespace prefix {:?}", prefix)
},
Error::InvalidQName { qname } => {
write!(f, "invalid QName {:?}", qname)
},
Error::FunctionEvaluation { fname } => {
write!(f, "error while evaluating function: {}", fname)
},
Error::Internal { msg } => {
write!(f, "internal error: {}", msg)
},
Error::JsException(exception) => {
write!(f, "JS exception: {:?}", exception)
},
}
}
}
impl std::error::Error for Error {}
pub(crate) fn try_extract_nodeset(v: Value) -> Result<Vec<DomRoot<Node>>, Error> {
match v {
Value::Nodeset(ns) => Ok(ns),
_ => Err(Error::NotANodeset),
}
}
pub(crate) trait Evaluatable: fmt::Debug {
fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error>;
/// Returns true if this expression evaluates to a primitive value, without needing to touch the DOM
fn is_primitive(&self) -> bool;
}
impl<T: ?Sized> Evaluatable for Box<T>
where
T: Evaluatable,
{
fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
(**self).evaluate(context)
}
fn is_primitive(&self) -> bool {
(**self).is_primitive()
}
}
impl<T> Evaluatable for Option<T>
where
T: Evaluatable,
{
fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
match self {
Some(expr) => expr.evaluate(context),
None => Ok(Value::Nodeset(vec![])),
}
}
fn is_primitive(&self) -> bool {
self.as_ref().is_some_and(|t| T::is_primitive(t))
}
}
impl Evaluatable for Expr {
fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
match self {
Expr::And(left, right) => {
let left_bool = left.evaluate(context)?.boolean();
let v = left_bool && right.evaluate(context)?.boolean();
Ok(Value::Boolean(v))
},
Expr::Or(left, right) => {
let left_bool = left.evaluate(context)?.boolean();
let v = left_bool || right.evaluate(context)?.boolean();
Ok(Value::Boolean(v))
},
Expr::Equality(left, equality_op, right) => {
let left_val = left.evaluate(context)?;
let right_val = right.evaluate(context)?;
let v = match equality_op {
EqualityOp::Eq => left_val == right_val,
EqualityOp::NotEq => left_val != right_val,
};
Ok(Value::Boolean(v))
},
Expr::Relational(left, relational_op, right) => {
let left_val = left.evaluate(context)?.number();
let right_val = right.evaluate(context)?.number();
let v = match relational_op {
RelationalOp::Lt => left_val < right_val,
RelationalOp::Gt => left_val > right_val,
RelationalOp::LtEq => left_val <= right_val,
RelationalOp::GtEq => left_val >= right_val,
};
Ok(Value::Boolean(v))
},
Expr::Additive(left, additive_op, right) => {
let left_val = left.evaluate(context)?.number();
let right_val = right.evaluate(context)?.number();
let v = match additive_op {
AdditiveOp::Add => left_val + right_val,
AdditiveOp::Sub => left_val - right_val,
};
Ok(Value::Number(v))
},
Expr::Multiplicative(left, multiplicative_op, right) => {
let left_val = left.evaluate(context)?.number();
let right_val = right.evaluate(context)?.number();
let v = match multiplicative_op {
MultiplicativeOp::Mul => left_val * right_val,
MultiplicativeOp::Div => left_val / right_val,
MultiplicativeOp::Mod => left_val % right_val,
};
Ok(Value::Number(v))
},
Expr::Unary(unary_op, expr) => {
let v = expr.evaluate(context)?.number();
match unary_op {
UnaryOp::Minus => Ok(Value::Number(-v)),
}
},
Expr::Union(left, right) => {
let as_nodes = |e: &Expr| e.evaluate(context).and_then(try_extract_nodeset);
let mut left_nodes = as_nodes(left)?;
let right_nodes = as_nodes(right)?;
left_nodes.extend(right_nodes);
Ok(Value::Nodeset(left_nodes))
},
Expr::Path(path_expr) => path_expr.evaluate(context),
}
}
fn is_primitive(&self) -> bool {
match self {
Expr::Or(left, right) => left.is_primitive() && right.is_primitive(),
Expr::And(left, right) => left.is_primitive() && right.is_primitive(),
Expr::Equality(left, _, right) => left.is_primitive() && right.is_primitive(),
Expr::Relational(left, _, right) => left.is_primitive() && right.is_primitive(),
Expr::Additive(left, _, right) => left.is_primitive() && right.is_primitive(),
Expr::Multiplicative(left, _, right) => left.is_primitive() && right.is_primitive(),
Expr::Unary(_, expr) => expr.is_primitive(),
Expr::Union(_, _) => false,
Expr::Path(path_expr) => path_expr.is_primitive(),
}
}
}
impl Evaluatable for PathExpr {
fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
// Use starting_node for absolute/descendant paths, context_node otherwise
let mut current_nodes = if self.is_absolute || self.is_descendant {
vec![context.starting_node.clone()]
} else {
vec![context.context_node.clone()]
};
// If path starts with '//', add an implicit descendant-or-self::node() step
if self.is_descendant {
current_nodes = current_nodes
.iter()
.flat_map(|n| n.traverse_preorder(ShadowIncluding::No))
.collect();
}
trace!("[PathExpr] Evaluating path expr: {:?}", self);
let have_multiple_steps = self.steps.len() > 1;
for step in &self.steps {
let mut next_nodes = Vec::new();
for node in current_nodes {
let step_context = context.subcontext_for_node(&node);
let step_result = step.evaluate(&step_context)?;
match (have_multiple_steps, step_result) {
(_, Value::Nodeset(mut nodes)) => {
// as long as we evaluate to nodesets, keep going
next_nodes.append(&mut nodes);
},
(false, value) => {
trace!("[PathExpr] Got single primitive value: {:?}", value);
return Ok(value);
},
(true, value) => {
error!(
"Expected nodeset from step evaluation, got: {:?} node: {:?}, step: {:?}",
value, node, step
);
return Ok(value);
},
}
}
current_nodes = next_nodes;
}
trace!("[PathExpr] Got nodes: {:?}", current_nodes);
Ok(Value::Nodeset(current_nodes))
}
fn is_primitive(&self) -> bool {
!self.is_absolute &&
!self.is_descendant &&
self.steps.len() == 1 &&
self.steps[0].is_primitive()
}
}
/// Error types for validate and extract a qualified name following
/// the XML naming rules.
#[derive(Debug)]
enum ValidationError {
InvalidCharacter,
Namespace,
}
/// Validate a qualified name following the XML naming rules.
///
/// On success, this returns a tuple `(prefix, local name)`.
fn validate_and_extract_qualified_name(
qualified_name: &str,
) -> Result<(Option<&str>, &str), ValidationError> {
if qualified_name.is_empty() {
// Qualified names must not be empty
return Err(ValidationError::InvalidCharacter);
}
let mut colon_offset = None;
let mut at_start_of_name = true;
for (byte_position, c) in qualified_name.char_indices() {
if c == ':' {
if colon_offset.is_some() {
// Qualified names must not contain more than one colon
return Err(ValidationError::InvalidCharacter);
}
colon_offset = Some(byte_position);
at_start_of_name = true;
continue;
}
if at_start_of_name {
if !xmlname::is_valid_start(c) {
// Name segments must begin with a valid start character
return Err(ValidationError::InvalidCharacter);
}
at_start_of_name = false;
} else if !xmlname::is_valid_continuation(c) {
// Name segments must consist of valid characters
return Err(ValidationError::InvalidCharacter);
}
}
let Some(colon_offset) = colon_offset else {
// Simple case: there is no prefix
return Ok((None, qualified_name));
};
let (prefix, local_name) = qualified_name.split_at(colon_offset);
let local_name = &local_name[1..]; // Remove the colon
if prefix.is_empty() || local_name.is_empty() {
// Neither prefix nor local name can be empty
return Err(ValidationError::InvalidCharacter);
}
Ok((Some(prefix), local_name))
}
/// Validate a namespace and qualified name following the XML naming rules
/// and extract their parts.
fn validate_and_extract(
namespace: Option<DOMString>,
qualified_name: &str,
) -> Result<(Namespace, Option<Prefix>, LocalName), ValidationError> {
// Step 1. If namespace is the empty string, then set it to null.
let namespace = namespace_from_domstring(namespace);
// Step 2. Validate qualifiedName.
// Step 3. Let prefix be null.
// Step 4. Let localName be qualifiedName.
// Step 5. If qualifiedName contains a U+003A (:):
// NOTE: validate_and_extract_qualified_name does all of these things for us, because
// it's easier to do them together
let (prefix, local_name) = validate_and_extract_qualified_name(qualified_name)?;
debug_assert!(!local_name.contains(':'));
match (namespace, prefix) {
(ns!(), Some(_)) => {
// Step 6. If prefix is non-null and namespace is null, then throw a "NamespaceError" DOMException.
Err(ValidationError::Namespace)
},
(ref ns, Some("xml")) if ns != &ns!(xml) => {
// Step 7. If prefix is "xml" and namespace is not the XML namespace,
// then throw a "NamespaceError" DOMException.
Err(ValidationError::Namespace)
},
(ref ns, p) if ns != &ns!(xmlns) && (qualified_name == "xmlns" || p == Some("xmlns")) => {
// Step 8. If either qualifiedName or prefix is "xmlns" and namespace is not the XMLNS namespace,
// then throw a "NamespaceError" DOMException.
Err(ValidationError::Namespace)
},
(ns!(xmlns), p) if qualified_name != "xmlns" && p != Some("xmlns") => {
// Step 9. If namespace is the XMLNS namespace and neither qualifiedName nor prefix is "xmlns",
// then throw a "NamespaceError" DOMException.
Err(ValidationError::Namespace)
},
(ns, p) => {
// Step 10. Return namespace, prefix, and localName.
Ok((ns, p.map(Prefix::from), LocalName::from(local_name)))
},
}
}
pub(crate) fn convert_parsed_qname_to_qualified_name(
qname: &ParserQualName,
context: &EvaluationCtx,
can_gc: CanGc,
) -> Result<QualName, Error> {
let qname_as_str = qname.to_string();
let namespace = context
.resolve_namespace(qname.prefix.as_deref(), can_gc)
.map_err(Error::JsException)?;
if let Ok((ns, prefix, local)) = validate_and_extract(namespace, &qname_as_str) {
Ok(QualName { prefix, ns, local })
} else {
Err(Error::InvalidQName {
qname: qname.clone(),
})
}
}
#[derive(Debug)]
pub(crate) enum NameTestComparisonMode {
/// Namespaces must match exactly
XHtml,
/// Missing namespace information is treated as the HTML namespace
Html,
}
pub(crate) fn element_name_test(
expected_name: QualName,
element_qualname: QualName,
comparison_mode: NameTestComparisonMode,
) -> bool {
let is_wildcard = expected_name.local == local_name!("*");
let test_prefix = expected_name
.prefix
.clone()
.unwrap_or(namespace_prefix!(""));
let test_ns_uri = match test_prefix {
namespace_prefix!("*") => ns!(*),
namespace_prefix!("html") => ns!(html),
namespace_prefix!("xml") => ns!(xml),
namespace_prefix!("xlink") => ns!(xlink),
namespace_prefix!("svg") => ns!(svg),
namespace_prefix!("mathml") => ns!(mathml),
namespace_prefix!("") => {
if matches!(comparison_mode, NameTestComparisonMode::XHtml) {
ns!()
} else {
ns!(html)
}
},
_ => {
// We don't support custom namespaces, use fallback or panic depending on strictness
if matches!(comparison_mode, NameTestComparisonMode::XHtml) {
panic!("Unrecognized namespace prefix: {}", test_prefix)
} else {
ns!(html)
}
},
};
if is_wildcard {
test_ns_uri == element_qualname.ns
} else {
test_ns_uri == element_qualname.ns && expected_name.local == element_qualname.local
}
}
fn apply_node_test(
context: &EvaluationCtx,
test: &NodeTest,
node: &Node,
can_gc: CanGc,
) -> Result<bool, Error> {
let result = match test {
NodeTest::Name(qname) => {
// Convert the unvalidated "parser QualName" into the proper QualName structure
let wanted_name = convert_parsed_qname_to_qualified_name(qname, context, can_gc)?;
match node.type_id() {
NodeTypeId::Element(_) => {
let element = node.downcast::<Element>().unwrap();
let comparison_mode = if node.owner_doc().is_html_document() {
NameTestComparisonMode::Html
} else {
NameTestComparisonMode::XHtml
};
let element_qualname = QualName::new(
element.prefix().as_ref().cloned(),
element.namespace().clone(),
element.local_name().clone(),
);
element_name_test(wanted_name, element_qualname, comparison_mode)
},
NodeTypeId::Attr => {
let attr = node.downcast::<Attr>().unwrap();
let attr_qualname = QualName::new(
attr.prefix().cloned(),
attr.namespace().clone(),
attr.local_name().clone(),
);
// attributes are always compared with strict namespace matching
let comparison_mode = NameTestComparisonMode::XHtml;
element_name_test(wanted_name, attr_qualname, comparison_mode)
},
_ => false,
}
},
NodeTest::Wildcard => matches!(node.type_id(), NodeTypeId::Element(_)),
NodeTest::Kind(kind) => match kind {
KindTest::PI(target) => {
if NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) ==
node.type_id()
{
let pi = node.downcast::<ProcessingInstruction>().unwrap();
match (target, pi.target()) {
(Some(target_name), node_target_name)
if target_name == &node_target_name.to_string() =>
{
true
},
(Some(_), _) => false,
(None, _) => true,
}
} else {
false
}
},
KindTest::Comment => matches!(
node.type_id(),
NodeTypeId::CharacterData(CharacterDataTypeId::Comment)
),
KindTest::Text => matches!(
node.type_id(),
NodeTypeId::CharacterData(CharacterDataTypeId::Text(_))
),
KindTest::Node => true,
},
};
Ok(result)
}
impl Evaluatable for StepExpr {
fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
match self {
StepExpr::Filter(filter_expr) => filter_expr.evaluate(context),
StepExpr::Axis(axis_step) => {
let nodes: Vec<DomRoot<Node>> = match axis_step.axis {
Axis::Child => context.context_node.children().collect(),
Axis::Descendant => context
.context_node
.traverse_preorder(ShadowIncluding::No)
.skip(1)
.collect(),
Axis::Parent => vec![context.context_node.GetParentNode()]
.into_iter()
.flatten()
.collect(),
Axis::Ancestor => context.context_node.ancestors().collect(),
Axis::Following => context
.context_node
.following_nodes(&context.context_node)
.skip(1)
.collect(),
Axis::Preceding => context
.context_node
.preceding_nodes(&context.context_node)
.skip(1)
.collect(),
Axis::FollowingSibling => context.context_node.following_siblings().collect(),
Axis::PrecedingSibling => context.context_node.preceding_siblings().collect(),
Axis::Attribute => {
if matches!(Node::type_id(&context.context_node), NodeTypeId::Element(_)) {
let element = context.context_node.downcast::<Element>().unwrap();
element
.attrs()
.iter()
.map(|attr| attr.upcast::<Node>())
.map(DomRoot::from_ref)
.collect()
} else {
vec![]
}
},
Axis::Self_ => vec![context.context_node.clone()],
Axis::DescendantOrSelf => context
.context_node
.traverse_preorder(ShadowIncluding::No)
.collect(),
Axis::AncestorOrSelf => context
.context_node
.inclusive_ancestors(ShadowIncluding::No)
.collect(),
Axis::Namespace => Vec::new(), // Namespace axis is not commonly implemented
};
trace!("[StepExpr] Axis {:?} got nodes {:?}", axis_step.axis, nodes);
// Filter nodes according to the step's node_test. Will error out if any NodeTest
// application errors out.
let filtered_nodes: Vec<DomRoot<Node>> = nodes
.into_iter()
.map(|node| {
// FIXME: propagate this can_gc up further. This likely requires removing the "Evaluate"
// trait or changing the signature of "evaluate". The trait is not really necessary anyways.
apply_node_test(context, &axis_step.node_test, &node, CanGc::note())
.map(|matches| matches.then_some(node))
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
.collect();
trace!("[StepExpr] Filtering got nodes {:?}", filtered_nodes);
if axis_step.predicates.predicates.is_empty() {
trace!(
"[StepExpr] No predicates, returning nodes {:?}",
filtered_nodes
);
Ok(Value::Nodeset(filtered_nodes))
} else {
// Apply predicates
let predicate_list_subcontext = context
.update_predicate_nodes(filtered_nodes.iter().map(|n| &**n).collect());
axis_step.predicates.evaluate(&predicate_list_subcontext)
}
},
}
}
fn is_primitive(&self) -> bool {
match self {
StepExpr::Filter(filter_expr) => filter_expr.is_primitive(),
StepExpr::Axis(_) => false,
}
}
}
impl Evaluatable for PredicateListExpr {
fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
if let Some(ref predicate_nodes) = context.predicate_nodes {
let mut matched_nodes: Vec<DomRoot<Node>> = predicate_nodes.clone();
for predicate_expr in &self.predicates {
let size = matched_nodes.len();
let mut new_matched = Vec::new();
for (i, node) in matched_nodes.iter().enumerate() {
// 1-based position, per XPath spec
let predicate_ctx = EvaluationCtx {
starting_node: context.starting_node.clone(),
context_node: node.clone(),
predicate_nodes: context.predicate_nodes.clone(),
predicate_ctx: Some(PredicateCtx { index: i + 1, size }),
resolver: context.resolver.clone(),
};
let eval_result = predicate_expr.expr.evaluate(&predicate_ctx);
let keep = match eval_result {
Ok(Value::Number(n)) => (i + 1) as f64 == n,
Ok(Value::Boolean(b)) => b,
Ok(v) => v.boolean(),
Err(_) => false,
};
if keep {
new_matched.push(node.clone());
}
}
matched_nodes = new_matched;
trace!(
"[PredicateListExpr] Predicate {:?} matched nodes {:?}",
predicate_expr, matched_nodes
);
}
Ok(Value::Nodeset(matched_nodes))
} else {
Err(Error::Internal {
msg: "[PredicateListExpr] No nodes on stack for predicate to operate on"
.to_string(),
})
}
}
fn is_primitive(&self) -> bool {
self.predicates.len() == 1 && self.predicates[0].is_primitive()
}
}
impl Evaluatable for PredicateExpr {
fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
let narrowed_nodes: Result<Vec<DomRoot<Node>>, Error> = context
.subcontext_iter_for_nodes()
.filter_map(|ctx| {
if let Some(predicate_ctx) = ctx.predicate_ctx {
let eval_result = self.expr.evaluate(&ctx);
let v = match eval_result {
Ok(Value::Number(v)) => Ok(predicate_ctx.index == v as usize),
Ok(Value::Boolean(v)) => Ok(v),
Ok(v) => Ok(v.boolean()),
Err(e) => Err(e),
};
match v {
Ok(true) => Some(Ok(ctx.context_node)),
Ok(false) => None,
Err(e) => Some(Err(e)),
}
} else {
Some(Err(Error::Internal {
msg: "[PredicateExpr] No predicate context set".to_string(),
}))
}
})
.collect();
Ok(Value::Nodeset(narrowed_nodes?))
}
fn is_primitive(&self) -> bool {
self.expr.is_primitive()
}
}
impl Evaluatable for FilterExpr {
fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
let primary_result = self.primary.evaluate(context)?;
let have_predicates = !self.predicates.predicates.is_empty();
match (have_predicates, &primary_result) {
(false, _) => {
trace!(
"[FilterExpr] No predicates, returning primary result: {:?}",
primary_result
);
Ok(primary_result)
},
(true, Value::Nodeset(vec)) => {
let predicate_list_subcontext =
context.update_predicate_nodes(vec.iter().map(|n| &**n).collect());
let result_filtered_by_predicates =
self.predicates.evaluate(&predicate_list_subcontext);
trace!(
"[FilterExpr] Result filtered by predicates: {:?}",
result_filtered_by_predicates
);
result_filtered_by_predicates
},
// You can't use filtering expressions `[]` on other than node-sets
(true, _) => Err(Error::NotANodeset),
}
}
fn is_primitive(&self) -> bool {
self.predicates.predicates.is_empty() && self.primary.is_primitive()
}
}
impl Evaluatable for PrimaryExpr {
fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
match self {
PrimaryExpr::Literal(literal) => literal.evaluate(context),
PrimaryExpr::Variable(_qname) => Err(Error::CannotUseVariables),
PrimaryExpr::Parenthesized(expr) => expr.evaluate(context),
PrimaryExpr::ContextItem => Ok(Value::Nodeset(vec![context.context_node.clone()])),
PrimaryExpr::Function(core_function) => core_function.evaluate(context),
}
}
fn is_primitive(&self) -> bool {
match self {
PrimaryExpr::Literal(_) => true,
PrimaryExpr::Variable(_qname) => false,
PrimaryExpr::Parenthesized(expr) => expr.is_primitive(),
PrimaryExpr::ContextItem => false,
PrimaryExpr::Function(_) => false,
}
}
}
impl Evaluatable for Literal {
fn evaluate(&self, _context: &EvaluationCtx) -> Result<Value, Error> {
match self {
Literal::Numeric(numeric_literal) => match numeric_literal {
// We currently make no difference between ints and floats
NumericLiteral::Integer(v) => Ok(Value::Number(*v as f64)),
NumericLiteral::Decimal(v) => Ok(Value::Number(*v)),
},
Literal::String(s) => Ok(Value::String(s.into())),
}
}
fn is_primitive(&self) -> bool {
true
}
}