script/xpath/
eval.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::fmt;
6
7use html5ever::{LocalName, Namespace, Prefix, QualName, local_name, namespace_prefix, ns};
8use script_bindings::script_runtime::CanGc;
9
10use super::parser::{
11    AdditiveOp, Axis, EqualityOp, Expr, FilterExpr, KindTest, Literal, MultiplicativeOp, NodeTest,
12    NumericLiteral, PathExpr, PredicateExpr, PredicateListExpr, PrimaryExpr,
13    QName as ParserQualName, RelationalOp, StepExpr, UnaryOp,
14};
15use super::{EvaluationCtx, Value};
16use crate::dom::attr::Attr;
17use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
18use crate::dom::bindings::domname::namespace_from_domstring;
19use crate::dom::bindings::error::Error as JsError;
20use crate::dom::bindings::inheritance::{Castable, CharacterDataTypeId, NodeTypeId};
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::bindings::str::DOMString;
23use crate::dom::bindings::xmlname;
24use crate::dom::element::Element;
25use crate::dom::node::{Node, ShadowIncluding};
26use crate::dom::processinginstruction::ProcessingInstruction;
27use crate::xpath::context::PredicateCtx;
28
29#[derive(Clone, Debug)]
30pub(crate) enum Error {
31    NotANodeset,
32    InvalidPath,
33    UnknownFunction {
34        name: QualName,
35    },
36    /// It is not clear where variables used in XPath expression should come from.
37    /// Firefox throws "NS_ERROR_ILLEGAL_VALUE" when using them, chrome seems to return
38    /// an empty result. We also error out.
39    ///
40    /// See <https://github.com/whatwg/dom/issues/67>
41    CannotUseVariables,
42    UnknownNamespace {
43        prefix: String,
44    },
45    InvalidQName {
46        qname: ParserQualName,
47    },
48    FunctionEvaluation {
49        fname: String,
50    },
51    Internal {
52        msg: String,
53    },
54    /// A JS exception that needs to be propagated to the caller.
55    JsException(JsError),
56}
57
58impl std::fmt::Display for Error {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        match self {
61            Error::NotANodeset => write!(f, "expression did not evaluate to a nodeset"),
62            Error::InvalidPath => write!(f, "invalid path expression"),
63            Error::UnknownFunction { name } => write!(f, "unknown function {:?}", name),
64            Error::CannotUseVariables => write!(f, "cannot use variables"),
65            Error::UnknownNamespace { prefix } => {
66                write!(f, "unknown namespace prefix {:?}", prefix)
67            },
68            Error::InvalidQName { qname } => {
69                write!(f, "invalid QName {:?}", qname)
70            },
71            Error::FunctionEvaluation { fname } => {
72                write!(f, "error while evaluating function: {}", fname)
73            },
74            Error::Internal { msg } => {
75                write!(f, "internal error: {}", msg)
76            },
77            Error::JsException(exception) => {
78                write!(f, "JS exception: {:?}", exception)
79            },
80        }
81    }
82}
83
84impl std::error::Error for Error {}
85
86pub(crate) fn try_extract_nodeset(v: Value) -> Result<Vec<DomRoot<Node>>, Error> {
87    match v {
88        Value::Nodeset(ns) => Ok(ns),
89        _ => Err(Error::NotANodeset),
90    }
91}
92
93pub(crate) trait Evaluatable: fmt::Debug {
94    fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error>;
95    /// Returns true if this expression evaluates to a primitive value, without needing to touch the DOM
96    fn is_primitive(&self) -> bool;
97}
98
99impl<T: ?Sized> Evaluatable for Box<T>
100where
101    T: Evaluatable,
102{
103    fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
104        (**self).evaluate(context)
105    }
106
107    fn is_primitive(&self) -> bool {
108        (**self).is_primitive()
109    }
110}
111
112impl<T> Evaluatable for Option<T>
113where
114    T: Evaluatable,
115{
116    fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
117        match self {
118            Some(expr) => expr.evaluate(context),
119            None => Ok(Value::Nodeset(vec![])),
120        }
121    }
122
123    fn is_primitive(&self) -> bool {
124        self.as_ref().is_some_and(|t| T::is_primitive(t))
125    }
126}
127
128impl Evaluatable for Expr {
129    fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
130        match self {
131            Expr::And(left, right) => {
132                let left_bool = left.evaluate(context)?.boolean();
133                let v = left_bool && right.evaluate(context)?.boolean();
134                Ok(Value::Boolean(v))
135            },
136            Expr::Or(left, right) => {
137                let left_bool = left.evaluate(context)?.boolean();
138                let v = left_bool || right.evaluate(context)?.boolean();
139                Ok(Value::Boolean(v))
140            },
141            Expr::Equality(left, equality_op, right) => {
142                let left_val = left.evaluate(context)?;
143                let right_val = right.evaluate(context)?;
144
145                let v = match equality_op {
146                    EqualityOp::Eq => left_val == right_val,
147                    EqualityOp::NotEq => left_val != right_val,
148                };
149
150                Ok(Value::Boolean(v))
151            },
152            Expr::Relational(left, relational_op, right) => {
153                let left_val = left.evaluate(context)?.number();
154                let right_val = right.evaluate(context)?.number();
155
156                let v = match relational_op {
157                    RelationalOp::Lt => left_val < right_val,
158                    RelationalOp::Gt => left_val > right_val,
159                    RelationalOp::LtEq => left_val <= right_val,
160                    RelationalOp::GtEq => left_val >= right_val,
161                };
162                Ok(Value::Boolean(v))
163            },
164            Expr::Additive(left, additive_op, right) => {
165                let left_val = left.evaluate(context)?.number();
166                let right_val = right.evaluate(context)?.number();
167
168                let v = match additive_op {
169                    AdditiveOp::Add => left_val + right_val,
170                    AdditiveOp::Sub => left_val - right_val,
171                };
172                Ok(Value::Number(v))
173            },
174            Expr::Multiplicative(left, multiplicative_op, right) => {
175                let left_val = left.evaluate(context)?.number();
176                let right_val = right.evaluate(context)?.number();
177
178                let v = match multiplicative_op {
179                    MultiplicativeOp::Mul => left_val * right_val,
180                    MultiplicativeOp::Div => left_val / right_val,
181                    MultiplicativeOp::Mod => left_val % right_val,
182                };
183                Ok(Value::Number(v))
184            },
185            Expr::Unary(unary_op, expr) => {
186                let v = expr.evaluate(context)?.number();
187
188                match unary_op {
189                    UnaryOp::Minus => Ok(Value::Number(-v)),
190                }
191            },
192            Expr::Union(left, right) => {
193                let as_nodes = |e: &Expr| e.evaluate(context).and_then(try_extract_nodeset);
194
195                let mut left_nodes = as_nodes(left)?;
196                let right_nodes = as_nodes(right)?;
197
198                left_nodes.extend(right_nodes);
199                Ok(Value::Nodeset(left_nodes))
200            },
201            Expr::Path(path_expr) => path_expr.evaluate(context),
202        }
203    }
204
205    fn is_primitive(&self) -> bool {
206        match self {
207            Expr::Or(left, right) => left.is_primitive() && right.is_primitive(),
208            Expr::And(left, right) => left.is_primitive() && right.is_primitive(),
209            Expr::Equality(left, _, right) => left.is_primitive() && right.is_primitive(),
210            Expr::Relational(left, _, right) => left.is_primitive() && right.is_primitive(),
211            Expr::Additive(left, _, right) => left.is_primitive() && right.is_primitive(),
212            Expr::Multiplicative(left, _, right) => left.is_primitive() && right.is_primitive(),
213            Expr::Unary(_, expr) => expr.is_primitive(),
214            Expr::Union(_, _) => false,
215            Expr::Path(path_expr) => path_expr.is_primitive(),
216        }
217    }
218}
219
220impl Evaluatable for PathExpr {
221    fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
222        // Use starting_node for absolute/descendant paths, context_node otherwise
223        let mut current_nodes = if self.is_absolute || self.is_descendant {
224            vec![context.starting_node.clone()]
225        } else {
226            vec![context.context_node.clone()]
227        };
228
229        // If path starts with '//', add an implicit descendant-or-self::node() step
230        if self.is_descendant {
231            current_nodes = current_nodes
232                .iter()
233                .flat_map(|n| n.traverse_preorder(ShadowIncluding::No))
234                .collect();
235        }
236
237        trace!("[PathExpr] Evaluating path expr: {:?}", self);
238
239        let have_multiple_steps = self.steps.len() > 1;
240
241        for step in &self.steps {
242            let mut next_nodes = Vec::new();
243            for node in current_nodes {
244                let step_context = context.subcontext_for_node(&node);
245                let step_result = step.evaluate(&step_context)?;
246                match (have_multiple_steps, step_result) {
247                    (_, Value::Nodeset(mut nodes)) => {
248                        // as long as we evaluate to nodesets, keep going
249                        next_nodes.append(&mut nodes);
250                    },
251                    (false, value) => {
252                        trace!("[PathExpr] Got single primitive value: {:?}", value);
253                        return Ok(value);
254                    },
255                    (true, value) => {
256                        error!(
257                            "Expected nodeset from step evaluation, got: {:?} node: {:?}, step: {:?}",
258                            value, node, step
259                        );
260                        return Ok(value);
261                    },
262                }
263            }
264            current_nodes = next_nodes;
265        }
266
267        trace!("[PathExpr] Got nodes: {:?}", current_nodes);
268
269        Ok(Value::Nodeset(current_nodes))
270    }
271
272    fn is_primitive(&self) -> bool {
273        !self.is_absolute &&
274            !self.is_descendant &&
275            self.steps.len() == 1 &&
276            self.steps[0].is_primitive()
277    }
278}
279
280/// Error types for validate and extract a qualified name following
281/// the XML naming rules.
282#[derive(Debug)]
283enum ValidationError {
284    InvalidCharacter,
285    Namespace,
286}
287
288/// Validate a qualified name following the XML naming rules.
289///
290/// On success, this returns a tuple `(prefix, local name)`.
291fn validate_and_extract_qualified_name(
292    qualified_name: &str,
293) -> Result<(Option<&str>, &str), ValidationError> {
294    if qualified_name.is_empty() {
295        // Qualified names must not be empty
296        return Err(ValidationError::InvalidCharacter);
297    }
298    let mut colon_offset = None;
299    let mut at_start_of_name = true;
300
301    for (byte_position, c) in qualified_name.char_indices() {
302        if c == ':' {
303            if colon_offset.is_some() {
304                // Qualified names must not contain more than one colon
305                return Err(ValidationError::InvalidCharacter);
306            }
307            colon_offset = Some(byte_position);
308            at_start_of_name = true;
309            continue;
310        }
311
312        if at_start_of_name {
313            if !xmlname::is_valid_start(c) {
314                // Name segments must begin with a valid start character
315                return Err(ValidationError::InvalidCharacter);
316            }
317            at_start_of_name = false;
318        } else if !xmlname::is_valid_continuation(c) {
319            // Name segments must consist of valid characters
320            return Err(ValidationError::InvalidCharacter);
321        }
322    }
323
324    let Some(colon_offset) = colon_offset else {
325        // Simple case: there is no prefix
326        return Ok((None, qualified_name));
327    };
328
329    let (prefix, local_name) = qualified_name.split_at(colon_offset);
330    let local_name = &local_name[1..]; // Remove the colon
331
332    if prefix.is_empty() || local_name.is_empty() {
333        // Neither prefix nor local name can be empty
334        return Err(ValidationError::InvalidCharacter);
335    }
336
337    Ok((Some(prefix), local_name))
338}
339
340/// Validate a namespace and qualified name following the XML naming rules
341/// and extract their parts.
342fn validate_and_extract(
343    namespace: Option<DOMString>,
344    qualified_name: &str,
345) -> Result<(Namespace, Option<Prefix>, LocalName), ValidationError> {
346    // Step 1. If namespace is the empty string, then set it to null.
347    let namespace = namespace_from_domstring(namespace);
348
349    // Step 2. Validate qualifiedName.
350    // Step 3. Let prefix be null.
351    // Step 4. Let localName be qualifiedName.
352    // Step 5. If qualifiedName contains a U+003A (:):
353    // NOTE: validate_and_extract_qualified_name does all of these things for us, because
354    // it's easier to do them together
355    let (prefix, local_name) = validate_and_extract_qualified_name(qualified_name)?;
356    debug_assert!(!local_name.contains(':'));
357
358    match (namespace, prefix) {
359        (ns!(), Some(_)) => {
360            // Step 6. If prefix is non-null and namespace is null, then throw a "NamespaceError" DOMException.
361            Err(ValidationError::Namespace)
362        },
363        (ref ns, Some("xml")) if ns != &ns!(xml) => {
364            // Step 7. If prefix is "xml" and namespace is not the XML namespace,
365            // then throw a "NamespaceError" DOMException.
366            Err(ValidationError::Namespace)
367        },
368        (ref ns, p) if ns != &ns!(xmlns) && (qualified_name == "xmlns" || p == Some("xmlns")) => {
369            // Step 8. If either qualifiedName or prefix is "xmlns" and namespace is not the XMLNS namespace,
370            // then throw a "NamespaceError" DOMException.
371            Err(ValidationError::Namespace)
372        },
373        (ns!(xmlns), p) if qualified_name != "xmlns" && p != Some("xmlns") => {
374            // Step 9. If namespace is the XMLNS namespace and neither qualifiedName nor prefix is "xmlns",
375            // then throw a "NamespaceError" DOMException.
376            Err(ValidationError::Namespace)
377        },
378        (ns, p) => {
379            // Step 10. Return namespace, prefix, and localName.
380            Ok((ns, p.map(Prefix::from), LocalName::from(local_name)))
381        },
382    }
383}
384
385pub(crate) fn convert_parsed_qname_to_qualified_name(
386    qname: &ParserQualName,
387    context: &EvaluationCtx,
388    can_gc: CanGc,
389) -> Result<QualName, Error> {
390    let qname_as_str = qname.to_string();
391    let namespace = context
392        .resolve_namespace(qname.prefix.as_deref(), can_gc)
393        .map_err(Error::JsException)?;
394
395    if let Ok((ns, prefix, local)) = validate_and_extract(namespace, &qname_as_str) {
396        Ok(QualName { prefix, ns, local })
397    } else {
398        Err(Error::InvalidQName {
399            qname: qname.clone(),
400        })
401    }
402}
403
404#[derive(Debug)]
405pub(crate) enum NameTestComparisonMode {
406    /// Namespaces must match exactly
407    XHtml,
408    /// Missing namespace information is treated as the HTML namespace
409    Html,
410}
411
412pub(crate) fn element_name_test(
413    expected_name: QualName,
414    element_qualname: QualName,
415    comparison_mode: NameTestComparisonMode,
416) -> bool {
417    let is_wildcard = expected_name.local == local_name!("*");
418
419    let test_prefix = expected_name
420        .prefix
421        .clone()
422        .unwrap_or(namespace_prefix!(""));
423    let test_ns_uri = match test_prefix {
424        namespace_prefix!("*") => ns!(*),
425        namespace_prefix!("html") => ns!(html),
426        namespace_prefix!("xml") => ns!(xml),
427        namespace_prefix!("xlink") => ns!(xlink),
428        namespace_prefix!("svg") => ns!(svg),
429        namespace_prefix!("mathml") => ns!(mathml),
430        namespace_prefix!("") => {
431            if matches!(comparison_mode, NameTestComparisonMode::XHtml) {
432                ns!()
433            } else {
434                ns!(html)
435            }
436        },
437        _ => {
438            // We don't support custom namespaces, use fallback or panic depending on strictness
439            if matches!(comparison_mode, NameTestComparisonMode::XHtml) {
440                panic!("Unrecognized namespace prefix: {}", test_prefix)
441            } else {
442                ns!(html)
443            }
444        },
445    };
446
447    if is_wildcard {
448        test_ns_uri == element_qualname.ns
449    } else {
450        test_ns_uri == element_qualname.ns && expected_name.local == element_qualname.local
451    }
452}
453
454fn apply_node_test(
455    context: &EvaluationCtx,
456    test: &NodeTest,
457    node: &Node,
458    can_gc: CanGc,
459) -> Result<bool, Error> {
460    let result = match test {
461        NodeTest::Name(qname) => {
462            // Convert the unvalidated "parser QualName" into the proper QualName structure
463            let wanted_name = convert_parsed_qname_to_qualified_name(qname, context, can_gc)?;
464            match node.type_id() {
465                NodeTypeId::Element(_) => {
466                    let element = node.downcast::<Element>().unwrap();
467                    let comparison_mode = if node.owner_doc().is_html_document() {
468                        NameTestComparisonMode::Html
469                    } else {
470                        NameTestComparisonMode::XHtml
471                    };
472                    let element_qualname = QualName::new(
473                        element.prefix().as_ref().cloned(),
474                        element.namespace().clone(),
475                        element.local_name().clone(),
476                    );
477                    element_name_test(wanted_name, element_qualname, comparison_mode)
478                },
479                NodeTypeId::Attr => {
480                    let attr = node.downcast::<Attr>().unwrap();
481                    let attr_qualname = QualName::new(
482                        attr.prefix().cloned(),
483                        attr.namespace().clone(),
484                        attr.local_name().clone(),
485                    );
486                    // attributes are always compared with strict namespace matching
487                    let comparison_mode = NameTestComparisonMode::XHtml;
488                    element_name_test(wanted_name, attr_qualname, comparison_mode)
489                },
490                _ => false,
491            }
492        },
493        NodeTest::Wildcard => matches!(node.type_id(), NodeTypeId::Element(_)),
494        NodeTest::Kind(kind) => match kind {
495            KindTest::PI(target) => {
496                if NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) ==
497                    node.type_id()
498                {
499                    let pi = node.downcast::<ProcessingInstruction>().unwrap();
500                    match (target, pi.target()) {
501                        (Some(target_name), node_target_name)
502                            if target_name == &node_target_name.to_string() =>
503                        {
504                            true
505                        },
506                        (Some(_), _) => false,
507                        (None, _) => true,
508                    }
509                } else {
510                    false
511                }
512            },
513            KindTest::Comment => matches!(
514                node.type_id(),
515                NodeTypeId::CharacterData(CharacterDataTypeId::Comment)
516            ),
517            KindTest::Text => matches!(
518                node.type_id(),
519                NodeTypeId::CharacterData(CharacterDataTypeId::Text(_))
520            ),
521            KindTest::Node => true,
522        },
523    };
524    Ok(result)
525}
526
527impl Evaluatable for StepExpr {
528    fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
529        match self {
530            StepExpr::Filter(filter_expr) => filter_expr.evaluate(context),
531            StepExpr::Axis(axis_step) => {
532                let nodes: Vec<DomRoot<Node>> = match axis_step.axis {
533                    Axis::Child => context.context_node.children().collect(),
534                    Axis::Descendant => context
535                        .context_node
536                        .traverse_preorder(ShadowIncluding::No)
537                        .skip(1)
538                        .collect(),
539                    Axis::Parent => vec![context.context_node.GetParentNode()]
540                        .into_iter()
541                        .flatten()
542                        .collect(),
543                    Axis::Ancestor => context.context_node.ancestors().collect(),
544                    Axis::Following => context
545                        .context_node
546                        .following_nodes(&context.context_node)
547                        .skip(1)
548                        .collect(),
549                    Axis::Preceding => context
550                        .context_node
551                        .preceding_nodes(&context.context_node)
552                        .skip(1)
553                        .collect(),
554                    Axis::FollowingSibling => context.context_node.following_siblings().collect(),
555                    Axis::PrecedingSibling => context.context_node.preceding_siblings().collect(),
556                    Axis::Attribute => {
557                        if matches!(Node::type_id(&context.context_node), NodeTypeId::Element(_)) {
558                            let element = context.context_node.downcast::<Element>().unwrap();
559                            element
560                                .attrs()
561                                .iter()
562                                .map(|attr| attr.upcast::<Node>())
563                                .map(DomRoot::from_ref)
564                                .collect()
565                        } else {
566                            vec![]
567                        }
568                    },
569                    Axis::Self_ => vec![context.context_node.clone()],
570                    Axis::DescendantOrSelf => context
571                        .context_node
572                        .traverse_preorder(ShadowIncluding::No)
573                        .collect(),
574                    Axis::AncestorOrSelf => context
575                        .context_node
576                        .inclusive_ancestors(ShadowIncluding::No)
577                        .collect(),
578                    Axis::Namespace => Vec::new(), // Namespace axis is not commonly implemented
579                };
580
581                trace!("[StepExpr] Axis {:?} got nodes {:?}", axis_step.axis, nodes);
582
583                // Filter nodes according to the step's node_test. Will error out if any NodeTest
584                // application errors out.
585                let filtered_nodes: Vec<DomRoot<Node>> = nodes
586                    .into_iter()
587                    .map(|node| {
588                        // FIXME: propagate this can_gc up further. This likely requires removing the "Evaluate"
589                        // trait or changing the signature of "evaluate". The trait is not really necessary anyways.
590                        apply_node_test(context, &axis_step.node_test, &node, CanGc::note())
591                            .map(|matches| matches.then_some(node))
592                    })
593                    .collect::<Result<Vec<_>, _>>()?
594                    .into_iter()
595                    .flatten()
596                    .collect();
597
598                trace!("[StepExpr] Filtering got nodes {:?}", filtered_nodes);
599
600                if axis_step.predicates.predicates.is_empty() {
601                    trace!(
602                        "[StepExpr] No predicates, returning nodes {:?}",
603                        filtered_nodes
604                    );
605                    Ok(Value::Nodeset(filtered_nodes))
606                } else {
607                    // Apply predicates
608                    let predicate_list_subcontext = context
609                        .update_predicate_nodes(filtered_nodes.iter().map(|n| &**n).collect());
610                    axis_step.predicates.evaluate(&predicate_list_subcontext)
611                }
612            },
613        }
614    }
615
616    fn is_primitive(&self) -> bool {
617        match self {
618            StepExpr::Filter(filter_expr) => filter_expr.is_primitive(),
619            StepExpr::Axis(_) => false,
620        }
621    }
622}
623
624impl Evaluatable for PredicateListExpr {
625    fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
626        if let Some(ref predicate_nodes) = context.predicate_nodes {
627            let mut matched_nodes: Vec<DomRoot<Node>> = predicate_nodes.clone();
628
629            for predicate_expr in &self.predicates {
630                let size = matched_nodes.len();
631                let mut new_matched = Vec::new();
632
633                for (i, node) in matched_nodes.iter().enumerate() {
634                    // 1-based position, per XPath spec
635                    let predicate_ctx = EvaluationCtx {
636                        starting_node: context.starting_node.clone(),
637                        context_node: node.clone(),
638                        predicate_nodes: context.predicate_nodes.clone(),
639                        predicate_ctx: Some(PredicateCtx { index: i + 1, size }),
640                        resolver: context.resolver.clone(),
641                    };
642
643                    let eval_result = predicate_expr.expr.evaluate(&predicate_ctx);
644
645                    let keep = match eval_result {
646                        Ok(Value::Number(n)) => (i + 1) as f64 == n,
647                        Ok(Value::Boolean(b)) => b,
648                        Ok(v) => v.boolean(),
649                        Err(_) => false,
650                    };
651
652                    if keep {
653                        new_matched.push(node.clone());
654                    }
655                }
656
657                matched_nodes = new_matched;
658                trace!(
659                    "[PredicateListExpr] Predicate {:?} matched nodes {:?}",
660                    predicate_expr, matched_nodes
661                );
662            }
663            Ok(Value::Nodeset(matched_nodes))
664        } else {
665            Err(Error::Internal {
666                msg: "[PredicateListExpr] No nodes on stack for predicate to operate on"
667                    .to_string(),
668            })
669        }
670    }
671
672    fn is_primitive(&self) -> bool {
673        self.predicates.len() == 1 && self.predicates[0].is_primitive()
674    }
675}
676
677impl Evaluatable for PredicateExpr {
678    fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
679        let narrowed_nodes: Result<Vec<DomRoot<Node>>, Error> = context
680            .subcontext_iter_for_nodes()
681            .filter_map(|ctx| {
682                if let Some(predicate_ctx) = ctx.predicate_ctx {
683                    let eval_result = self.expr.evaluate(&ctx);
684
685                    let v = match eval_result {
686                        Ok(Value::Number(v)) => Ok(predicate_ctx.index == v as usize),
687                        Ok(Value::Boolean(v)) => Ok(v),
688                        Ok(v) => Ok(v.boolean()),
689                        Err(e) => Err(e),
690                    };
691
692                    match v {
693                        Ok(true) => Some(Ok(ctx.context_node)),
694                        Ok(false) => None,
695                        Err(e) => Some(Err(e)),
696                    }
697                } else {
698                    Some(Err(Error::Internal {
699                        msg: "[PredicateExpr] No predicate context set".to_string(),
700                    }))
701                }
702            })
703            .collect();
704
705        Ok(Value::Nodeset(narrowed_nodes?))
706    }
707
708    fn is_primitive(&self) -> bool {
709        self.expr.is_primitive()
710    }
711}
712
713impl Evaluatable for FilterExpr {
714    fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
715        let primary_result = self.primary.evaluate(context)?;
716        let have_predicates = !self.predicates.predicates.is_empty();
717
718        match (have_predicates, &primary_result) {
719            (false, _) => {
720                trace!(
721                    "[FilterExpr] No predicates, returning primary result: {:?}",
722                    primary_result
723                );
724                Ok(primary_result)
725            },
726            (true, Value::Nodeset(vec)) => {
727                let predicate_list_subcontext =
728                    context.update_predicate_nodes(vec.iter().map(|n| &**n).collect());
729                let result_filtered_by_predicates =
730                    self.predicates.evaluate(&predicate_list_subcontext);
731                trace!(
732                    "[FilterExpr] Result filtered by predicates: {:?}",
733                    result_filtered_by_predicates
734                );
735                result_filtered_by_predicates
736            },
737            // You can't use filtering expressions `[]` on other than node-sets
738            (true, _) => Err(Error::NotANodeset),
739        }
740    }
741
742    fn is_primitive(&self) -> bool {
743        self.predicates.predicates.is_empty() && self.primary.is_primitive()
744    }
745}
746
747impl Evaluatable for PrimaryExpr {
748    fn evaluate(&self, context: &EvaluationCtx) -> Result<Value, Error> {
749        match self {
750            PrimaryExpr::Literal(literal) => literal.evaluate(context),
751            PrimaryExpr::Variable(_qname) => Err(Error::CannotUseVariables),
752            PrimaryExpr::Parenthesized(expr) => expr.evaluate(context),
753            PrimaryExpr::ContextItem => Ok(Value::Nodeset(vec![context.context_node.clone()])),
754            PrimaryExpr::Function(core_function) => core_function.evaluate(context),
755        }
756    }
757
758    fn is_primitive(&self) -> bool {
759        match self {
760            PrimaryExpr::Literal(_) => true,
761            PrimaryExpr::Variable(_qname) => false,
762            PrimaryExpr::Parenthesized(expr) => expr.is_primitive(),
763            PrimaryExpr::ContextItem => false,
764            PrimaryExpr::Function(_) => false,
765        }
766    }
767}
768
769impl Evaluatable for Literal {
770    fn evaluate(&self, _context: &EvaluationCtx) -> Result<Value, Error> {
771        match self {
772            Literal::Numeric(numeric_literal) => match numeric_literal {
773                // We currently make no difference between ints and floats
774                NumericLiteral::Integer(v) => Ok(Value::Number(*v as f64)),
775                NumericLiteral::Decimal(v) => Ok(Value::Number(*v)),
776            },
777            Literal::String(s) => Ok(Value::String(s.into())),
778        }
779    }
780
781    fn is_primitive(&self) -> bool {
782        true
783    }
784}