chadpath 0.3.3

XPath 1.0 / XSLT engine — a fork of xrust (Apache-2.0) with XPath positional-predicate correctness fixes and parser performance improvements. Used by chadselect.
Documentation
//! These functions are for features defined in XPath Functions 1.0 and 2.0.

use std::rc::Rc;
use url::Url;

use crate::item::{Item, Node, Sequence, SequenceTrait};
use crate::transform::Transform;
use crate::transform::context::{Context, StaticContext};
use crate::value::{Operator, Value};
use crate::xdmerror::{Error, ErrorKind};

thread_local! {
    // The boolean values `true`/`false` are immutable and produced constantly by
    // comparison operators (every predicate `[a=b]` returns one). Interning a
    // single `Rc<Value>` for each and cloning the `Rc` (a refcount bump) replaces
    // a heap allocation per comparison with none. `Value` is not parameterised by
    // the node type, so one cache serves every `Node` impl.
    static TRUE_VALUE: Rc<Value> = Rc::new(Value::from(true));
    static FALSE_VALUE: Rc<Value> = Rc::new(Value::from(false));
}

/// A one-item boolean result sequence, reusing a cached `Rc<Value>` so no heap
/// allocation is made for the boolean itself (the `Vec` is still required by the
/// `Sequence` return type).
#[inline]
fn bool_seq<N: Node>(b: bool) -> Sequence<N> {
    let v = if b {
        TRUE_VALUE.with(|t| t.clone())
    } else {
        FALSE_VALUE.with(|f| f.clone())
    };
    vec![Item::Value(v)]
}

/// Return the disjunction of all of the given functions.
pub(crate) fn tr_or<
    N: Node,
    F: FnMut(&str) -> Result<(), Error>,
    G: FnMut(&str) -> Result<N, Error>,
    H: FnMut(&Url) -> Result<String, Error>,
>(
    ctxt: &Context<N>,
    stctxt: &mut StaticContext<N, F, G, H>,
    v: &Vec<Transform<N>>,
) -> Result<Sequence<N>, Error> {
    // Future: Evaluate every operand to check for dynamic errors
    let mut b = false;
    let mut i = 0;
    while let Some(a) = v.get(i) {
        if ctxt.dispatch(stctxt, a)?.to_bool() {
            b = true;
            break;
        }
        i += 1;
    }
    Ok(vec![Item::Value(Rc::new(Value::from(b)))])
}

/// Return the conjunction of all of the given functions.
pub(crate) fn tr_and<
    N: Node,
    F: FnMut(&str) -> Result<(), Error>,
    G: FnMut(&str) -> Result<N, Error>,
    H: FnMut(&Url) -> Result<String, Error>,
>(
    ctxt: &Context<N>,
    stctxt: &mut StaticContext<N, F, G, H>,
    v: &Vec<Transform<N>>,
) -> Result<Sequence<N>, Error> {
    // Future: Evaluate every operand to check for dynamic errors
    let mut b = true;
    let mut i = 0;
    while let Some(a) = v.get(i) {
        if !ctxt.dispatch(stctxt, a)?.to_bool() {
            b = false;
            break;
        }
        i += 1;
    }
    Ok(vec![Item::Value(Rc::new(Value::from(b)))])
}

/// General comparison of two sequences.
pub(crate) fn general_comparison<
    N: Node,
    F: FnMut(&str) -> Result<(), Error>,
    G: FnMut(&str) -> Result<N, Error>,
    H: FnMut(&Url) -> Result<String, Error>,
>(
    ctxt: &Context<N>,
    stctxt: &mut StaticContext<N, F, G, H>,
    o: &Operator,
    l: &Transform<N>,
    r: &Transform<N>,
) -> Result<Sequence<N>, Error> {
    let left = ctxt.dispatch(stctxt, l)?;
    let right = ctxt.dispatch(stctxt, r)?;

    // XPath 1.0 §3.4: the relational operators (`<`, `<=`, `>`, `>=`) compare by
    // converting BOTH operands to numbers (for node-sets, each node's
    // string-value is converted). Only `=`/`!=` use the type-based string/
    // number/boolean rules. This is the GENERAL-comparison path; value
    // comparison (`lt`/`gt`) keeps string semantics via `value_comparison`.
    let relational = matches!(
        o,
        Operator::LessThan
            | Operator::LessThanEqual
            | Operator::GreaterThan
            | Operator::GreaterThanEqual
    );

    let mut b = false;
    'outer: for i in &left {
        for j in &right {
            b = if relational {
                let (x, y) = (i.to_double(), j.to_double());
                match o {
                    Operator::LessThan => x < y,
                    Operator::LessThanEqual => x <= y,
                    Operator::GreaterThan => x > y,
                    Operator::GreaterThanEqual => x >= y,
                    _ => unreachable!(),
                }
            } else {
                i.compare(j, *o)?
            };
            if b {
                break 'outer;
            }
        }
    }

    Ok(bool_seq(b))
}

/// Value comparison of two singleton sequences.
pub(crate) fn value_comparison<
    N: Node,
    F: FnMut(&str) -> Result<(), Error>,
    G: FnMut(&str) -> Result<N, Error>,
    H: FnMut(&Url) -> Result<String, Error>,
>(
    ctxt: &Context<N>,
    stctxt: &mut StaticContext<N, F, G, H>,
    o: &Operator,
    l: &Transform<N>,
    r: &Transform<N>,
) -> Result<Sequence<N>, Error> {
    let left = ctxt.dispatch(stctxt, l)?;
    if left.len() != 1 {
        return Err(Error::new(
            ErrorKind::TypeError,
            String::from("left-hand sequence is not a singleton sequence"),
        ));
    }
    let right = ctxt.dispatch(stctxt, r)?;
    if right.len() != 1 {
        return Err(Error::new(
            ErrorKind::TypeError,
            String::from("right-hand sequence is not a singleton sequence"),
        ));
    }

    Ok(vec![Item::Value(Rc::new(Value::from(
        left[0].compare(&right[0], *o)?,
    )))])
}

/// Each function in the supplied vector is evaluated, and the resulting sequences are combined into a single sequence.
/// All items must be nodes.
/// TODO: eliminate duplicates, sort by document order (XPath 3.1 3.4.2).
pub(crate) fn union<
    N: Node,
    F: FnMut(&str) -> Result<(), Error>,
    G: FnMut(&str) -> Result<N, Error>,
    H: FnMut(&Url) -> Result<String, Error>,
>(
    ctxt: &Context<N>,
    stctxt: &mut StaticContext<N, F, G, H>,
    branches: &Vec<Transform<N>>,
) -> Result<Sequence<N>, Error> {
    let mut result = vec![];
    for b in branches {
        let mut c = ctxt.dispatch(stctxt, b)?;
        if c.iter().any(|f| !f.is_node()) {
            return Err(Error::new(
                ErrorKind::TypeError,
                "all operands must be nodes",
            ));
        }
        result.append(&mut c)
    }
    // TODO: eliminate duplicates and sort into document order
    Ok(result)
}