PureXML/Sources/XPath/Query.swift
Mihaela Mihaljevic 4fe8771fb9 feat(xpath): explicit evaluation context entry point
Add Query.value(at:position:size:variables:) to evaluate an expression
against a context node already in a tree, with its proximity position, size,
and variable bindings. position(), last(), and the upward axes resolve from
that node. This completes full XPath 1.0: the per-node entry point that
downstream engines (XSLT, Schematron) drive.

Closes #43
2026-06-08 05:40:52 +02:00

71 lines
3.3 KiB
Swift

public extension PureXML.XPath {
/// A compiled XPath 1.0 expression: all thirteen axes with their
/// abbreviations, the node tests, the operator grammar (`or`, `and`, `=`,
/// `!=`, `<`, `<=`, `>`, `>=`, `+`, `-`, `*`, `div`, `mod`, unary `-`, `|`),
/// number and string literals, variable references, function calls, and
/// filter expressions, all over the four-type model (node-set, boolean,
/// number, string). Reimplemented from the XPath 1.0 specification.
struct Query: Sendable {
let expression: Expression
/// Compiles an XPath expression, throwing ``QueryError`` on a syntax error.
public init(_ path: String) throws {
expression = try Compiler.compile(path)
}
/// Evaluates the query over a node, returning the selected node-set in
/// document order. A non-node-set expression yields an empty list; use
/// ``value(over:variables:)`` for typed results.
public func evaluate(over node: PureXML.Model.Node) -> [Selection] {
Evaluator.evaluate(expression, over: node)
}
/// Evaluates the query and returns the selected elements only.
public func elements(over node: PureXML.Model.Node) -> [PureXML.Model.Element] {
evaluate(over: node).compactMap(\.element)
}
/// Evaluates the query and returns the string-value of each selection.
public func strings(over node: PureXML.Model.Node) -> [String] {
evaluate(over: node).map(\.stringValue)
}
/// Evaluates the query to a typed ``Value`` with optional variable bindings.
public func value(
over node: PureXML.Model.Node,
variables: [String: Value] = [:],
) throws -> Value {
try Evaluator.value(expression, over: node, variables: variables)
}
/// Evaluates the query against an explicit context: a node already in a
/// tree (``PureXML/Model/TreeNode``), its one-based proximity `position`
/// within a node-set of `size`, and variable bindings. Downstream engines
/// (XSLT, Schematron) drive this per context node; `position()` and
/// `last()` reflect the supplied values.
public func value(
at node: PureXML.Model.TreeNode,
position: Int = 1,
size: Int = 1,
variables: [String: Value] = [:],
) throws -> Value {
try Evaluator.value(expression, at: node, position: position, size: size, variables: variables)
}
/// Evaluates the query and coerces the result to a number.
public func number(over node: PureXML.Model.Node, variables: [String: Value] = [:]) throws -> Double {
try value(over: node, variables: variables).number
}
/// Evaluates the query and coerces the result to a string.
public func string(over node: PureXML.Model.Node, variables: [String: Value] = [:]) throws -> String {
try value(over: node, variables: variables).string
}
/// Evaluates the query and coerces the result to a boolean.
public func boolean(over node: PureXML.Model.Node, variables: [String: Value] = [:]) throws -> Bool {
try value(over: node, variables: variables).boolean
}
}
}