Skip to main content

libxml_rs/xml/xpath/
mod.rs

1//! XPath 1.0 implementation (§25, §85 Phase 5).
2//!
3//! Complete XPath 1.0 engine: location paths, axes, node tests, predicates,
4//! functions, variables, namespaces, node sets, boolean/number/string
5//! conversion, comparison semantics, NaN/infinity/negative zero, document
6//! order, context position/size, extension functions, compiled expressions.
7//!
8//! # Modules
9//!
10//! - `ast` — expression AST types (axes, node tests, steps, operators)
11//! - `lexer` — tokenizer
12//! - `parser` — recursive descent parser
13//! - `types` — XPath value types (node sets, numbers, strings, booleans)
14//! - `context` — evaluation context (variables, namespaces, functions)
15//! - `axes` — axis traversal
16//! - `functions` — core function library (25 XPath 1.0 functions)
17//! - `eval` — evaluation engine
18//!
19//! # Upstream contract
20//!
21//! Mirrors upstream `xpath.c` / `xpathInternals.h`
22//! (`SRC-LIBXML2-2.15.0-XPATH-C`, parity target libxml2 2.15.3 oracle):
23//! expression compilation, the 13 axes, node tests, predicates, the core
24//! function library, value stack semantics and the `xmlXPathObject` /
25//! `xmlXPathCompExpr` / `xmlXPathContext` C types (R-000126 restored the
26//! full xpathInternals.h header surface; R-000128 fixed the opLimit/
27//! opCount field types).
28//!
29//! # Conceptual behavior
30//!
31//! The engine implements XPath 1.0 (W3C-XPATH-1.0): the lexer/parser build
32//! an AST, `eval` walks it with document-order node-set semantics,
33//! IEEE 754 number handling (NaN/infinity/negative zero), boolean/string
34//! conversion per §4, context position/size, and the extension-function
35//! registry bridged to C callbacks (R-000162).
36//!
37//! # Ownership & safety invariants
38//!
39//! `xmlXPathCompile` results are freed with `xmlXPathFreeCompExpr`; XPath
40//! objects with `xmlXPathFreeObject` (object owns its node-set/string/
41//! number storage). Node-set entries are borrowed tree pointers — the tree
42//! outlives evaluation (types.rs `XPathNode` SAFETY note). The parser
43//! context bridge owns the value stack and the popped-string copy
44//! (R-000169 fixed xml_strdup on a non-NUL-terminated Rust String).
45//!
46//! # Historical quirks & epochs
47//!
48//! R-000102 (absolute paths evaluate from the document root node),
49//! R-000159 (predicate position() semantics) and R-000166 (number
50//! formatting, 1e9/1e-5 threshold, DBL_DIG=15) are upstream behaviors
51//! locked by residuals. The node-set dump became newline-separated in the
52//! 2.9.10 epoch (E-001, commit da35eeae, an upstream-documented breaking
53//! change); the empty-node-set exit-code epochs (E-003) sit at the
54//! xmllint layer this engine feeds.
55//!
56//! # Deliberate oddities
57//!
58//! `xmlXPathCastNumberToString` trailing-zero trimming, the integer
59//! shortcut and the `e+NN`/`e-NN` exponent form are reproduced exactly
60//! (R-000166) rather than delegating to Rust float formatting, which
61//! differs on exponent width and trimming.
62//!
63//! # Proving courts
64//!
65//! XPATH / XPOINTER / XINCLUDE court families, XPATH-001 differential
66//! probes (courts/suites/data-abi/*), the 967/967 number() corpus and
67//! CLI-XSLTPROC-0014/0015/0017 (format-number) require byte-identical
68//! output vs the oracle; cargo test runs the engine suites.
69//!
70//! # Tempting simplifications that would break parity
71//!
72//! Do not swap number formatting to `format!("{}")`: the 1e9/1e-5
73//! scientific threshold, 15-digit fraction computation and exponent
74//! padding are oracle-observable (R-000166). Do not deduplicate node-sets
75//! by value instead of pointer, and do not drop the borrowed-node
76//! invariant — the C ABI hands out raw node pointers.
77
78pub mod ast;
79pub mod axes;
80pub mod context;
81pub mod eval;
82pub mod exports;
83pub mod functions;
84pub mod lexer;
85pub mod parser;
86pub mod parser_context;
87pub mod types;
88
89use ast::CompiledExpr;
90use context::XPathContext;
91use parser::parse_xpath;
92use types::XPathValue;
93
94/// Parse and compile an XPath expression string.
95///
96/// Returns `None` on parse error.
97pub fn compile(expr_str: &str) -> Option<CompiledExpr> {
98    compile_result(expr_str).ok()
99}
100
101/// Parse and compile an XPath expression string, exposing the parse error
102/// (message + byte offset) for upstream-faithful diagnostics
103/// (HOSTILE-FAILURE F3).
104pub fn compile_result(
105    expr_str: &str,
106) -> Result<CompiledExpr, crate::xml::xpath::parser::ParseError> {
107    parse_xpath(expr_str).map(|expr| CompiledExpr::new(expr_str.to_string(), expr))
108}
109
110/// Evaluate a compiled XPath expression.
111///
112/// Returns `None` on evaluation error; the error message is recorded on the
113/// context (`XPathContext::error`) so callers can surface it exactly as
114/// upstream does ("XPath error : ...").
115pub fn evaluate(compiled: &CompiledExpr, context: &mut XPathContext) -> Option<XPathValue> {
116    match eval::eval(context, &compiled.expr) {
117        Ok(value) => Some(value),
118        Err(msg) => {
119            context.set_error(&msg);
120            None
121        }
122    }
123}
124
125/// Parse and evaluate in one step.
126pub fn evaluate_str(expr_str: &str, context: &mut XPathContext) -> Option<XPathValue> {
127    compile(expr_str).and_then(|compiled| evaluate(&compiled, context))
128}