libxml_rs/xml/xpath/ast.rs
1//! XPath 1.0 Expression AST (§25).
2//!
3//! Internal Rust representation of XPath expressions, separate from the C ABI.
4//! The parser builds this AST; the evaluator walks it.
5//!
6//! # UPSTREAM-PARITY
7//!
8//! Covers the full XPath 1.0 grammar:
9//! - Location paths (relative/absolute, steps, axes, node tests, predicates)
10//! - Operators (union, comparison, boolean, arithmetic)
11//! - Functions, variables, literals
12//! - Filter expressions (primary with predicates)
13
14use std::fmt;
15
16// ═══════════════════════════════════════════════════════════════════════════════
17// Axes
18// ═══════════════════════════════════════════════════════════════════════════════
19
20/// XPath 1.0 axis.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum Axis {
23 /// `ancestor` — all ancestors of the context node (parent, grandparent, …)
24 Ancestor,
25 /// `ancestor-or-self` — all ancestors plus the context node itself
26 AncestorOrSelf,
27 /// `attribute` — the attributes of the context node
28 Attribute,
29 /// `child` — the children of the context node
30 Child,
31 /// `descendant` — all descendants of the context node (children, grandchildren, …)
32 Descendant,
33 /// `descendant-or-self` — all descendants plus the context node itself
34 DescendantOrSelf,
35 /// `following` — every node after the context node in document order, excluding descendants
36 Following,
37 /// `following-sibling` — the siblings that follow the context node
38 FollowingSibling,
39 /// `namespace` — the namespace nodes of the context node
40 Namespace,
41 /// `parent` — the parent of the context node (at most one node)
42 Parent,
43 /// `preceding` — every node before the context node in document order, excluding ancestors
44 Preceding,
45 /// `preceding-sibling` — the siblings that precede the context node
46 PrecedingSibling,
47 /// `self` — the context node itself
48 Self_,
49}
50
51impl Axis {
52 /// All 13 XPath 1.0 axes.
53 pub const ALL: &'static [Axis] = &[
54 Axis::Ancestor,
55 Axis::AncestorOrSelf,
56 Axis::Attribute,
57 Axis::Child,
58 Axis::Descendant,
59 Axis::DescendantOrSelf,
60 Axis::Following,
61 Axis::FollowingSibling,
62 Axis::Namespace,
63 Axis::Parent,
64 Axis::Preceding,
65 Axis::PrecedingSibling,
66 Axis::Self_,
67 ];
68
69 /// Return the axis name as written in an XPath expression
70 /// (e.g. `"ancestor-or-self"`).
71 pub const fn as_str(&self) -> &'static str {
72 match self {
73 Axis::Ancestor => "ancestor",
74 Axis::AncestorOrSelf => "ancestor-or-self",
75 Axis::Attribute => "attribute",
76 Axis::Child => "child",
77 Axis::Descendant => "descendant",
78 Axis::DescendantOrSelf => "descendant-or-self",
79 Axis::Following => "following",
80 Axis::FollowingSibling => "following-sibling",
81 Axis::Namespace => "namespace",
82 Axis::Parent => "parent",
83 Axis::Preceding => "preceding",
84 Axis::PrecedingSibling => "preceding-sibling",
85 Axis::Self_ => "self",
86 }
87 }
88}
89
90impl fmt::Display for Axis {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 write!(f, "{}", self.as_str())
93 }
94}
95
96// ═══════════════════════════════════════════════════════════════════════════════
97// Node Tests
98// ═══════════════════════════════════════════════════════════════════════════════
99
100/// XPath node test.
101#[derive(Debug, Clone, PartialEq)]
102pub enum NodeTest {
103 /// name() — matches any node of principal node type
104 NameTest(NameTest),
105 /// comment()
106 Comment,
107 /// text()
108 Text,
109 /// processing-instruction() or processing-instruction("target")
110 ProcessingInstruction(Option<String>),
111 /// node()
112 Node,
113 /// * — matches any node of principal node type
114 Wildcard,
115 /// prefix:* — namespace wildcard
116 NsWildcard(String),
117}
118
119/// A name test (QName or wildcard).
120#[derive(Debug, Clone, PartialEq)]
121pub enum NameTest {
122 /// Just a local name: "para"
123 LocalName(String),
124 /// Qualified name: "xslt:template"
125 QName {
126 /// Namespace prefix, e.g. `xslt` in `xslt:template`
127 prefix: String,
128 /// Local part, e.g. `template` in `xslt:template`
129 local: String,
130 },
131 /// Wildcard name: *
132 Any,
133}
134
135impl fmt::Display for NodeTest {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 match self {
138 NodeTest::NameTest(n) => match n {
139 NameTest::LocalName(s) => write!(f, "{}", s),
140 NameTest::QName { prefix, local } => write!(f, "{}:{}", prefix, local),
141 NameTest::Any => write!(f, "*"),
142 },
143 NodeTest::Comment => write!(f, "comment()"),
144 NodeTest::Text => write!(f, "text()"),
145 NodeTest::ProcessingInstruction(None) => write!(f, "processing-instruction()"),
146 NodeTest::ProcessingInstruction(Some(t)) => {
147 write!(f, "processing-instruction('{}')", t)
148 }
149 NodeTest::Node => write!(f, "node()"),
150 NodeTest::Wildcard => write!(f, "*"),
151 NodeTest::NsWildcard(prefix) => write!(f, "{}:*", prefix),
152 }
153 }
154}
155
156// ═══════════════════════════════════════════════════════════════════════════════
157// Step
158// ═══════════════════════════════════════════════════════════════════════════════
159
160/// A single location step: axis::node-test[predicates]
161#[derive(Debug, Clone, PartialEq)]
162pub struct Step {
163 /// Axis the step traverses (defaults to `child` for abbreviated steps)
164 pub axis: Axis,
165 /// Node test selecting which nodes the step matches
166 pub node_test: NodeTest,
167 /// Predicates filtering the selected nodes, applied left to right
168 pub predicates: Vec<Expr>,
169}
170
171// ═══════════════════════════════════════════════════════════════════════════════
172// Binary Operators
173// ═══════════════════════════════════════════════════════════════════════════════
174
175/// XPath 1.0 binary operator.
176#[derive(Debug, Clone, Copy, PartialEq)]
177pub enum BinaryOp {
178 /// `|` — union
179 Union,
180 /// `and`
181 And,
182 /// `or`
183 Or,
184 /// `=`
185 Eq,
186 /// `!=`
187 Ne,
188 /// `<`
189 Lt,
190 /// `>`
191 Gt,
192 /// `<=`
193 Le,
194 /// `>=`
195 Ge,
196 /// `+`
197 Add,
198 /// `-`
199 Sub,
200 /// `*` (multiplication)
201 Mul,
202 /// `div`
203 Div,
204 /// `mod`
205 Mod,
206}
207
208// ═══════════════════════════════════════════════════════════════════════════════
209// Expressions
210// ═══════════════════════════════════════════════════════════════════════════════
211
212/// XPath expression AST node.
213#[derive(Debug, Clone, PartialEq)]
214pub enum Expr {
215 /// Absolute location path: `/expr`
216 AbsolutePath(Box<Expr>),
217 /// Relative location path: `step1/step2`
218 RelativePath(Box<Expr>, Box<Expr>),
219 /// A single step
220 Step(Step),
221 /// Filter expression: `primary[pred1][pred2]`
222 Filter(Box<Expr>, Vec<Expr>),
223 /// Variable reference: `$name`
224 Variable(String),
225 /// String literal: `'hello'`
226 StringLiteral(String),
227 /// Numeric literal: `42` or `3.14`
228 NumberLiteral(f64),
229 /// Boolean literal
230 BooleanLiteral(bool),
231 /// Function call: `name(arg1, arg2)`
232 FunctionCall {
233 /// Function name (a QName, possibly with a namespace prefix)
234 name: String,
235 /// Argument expressions, evaluated in order
236 args: Vec<Expr>,
237 },
238 /// Binary operation: `left op right`
239 BinaryOp {
240 /// The operator applied to the two operands
241 op: BinaryOp,
242 /// Left operand
243 left: Box<Expr>,
244 /// Right operand
245 right: Box<Expr>,
246 },
247 /// Unary minus: `-expr`
248 UnaryMinus(Box<Expr>),
249 /// Union expression: `left | right`
250 Union(Box<Expr>, Box<Expr>),
251}
252
253impl Expr {
254 /// Check if this expression is a location path (returns nodeset).
255 pub const fn is_location_path(&self) -> bool {
256 matches!(
257 self,
258 Expr::AbsolutePath(_) | Expr::RelativePath(_, _) | Expr::Step(_) | Expr::Filter(_, _)
259 )
260 }
261
262 /// Check if this is a constant value (no evaluation needed).
263 pub const fn is_constant(&self) -> bool {
264 matches!(
265 self,
266 Expr::StringLiteral(_) | Expr::NumberLiteral(_) | Expr::BooleanLiteral(_)
267 )
268 }
269}
270
271// ═══════════════════════════════════════════════════════════════════════════════
272// Compiled Expression
273// ═══════════════════════════════════════════════════════════════════════════════
274
275/// A compiled XPath expression.
276///
277/// Internal representation, not the C ABI `xmlXPathCompExprPtr`.
278#[derive(Debug, Clone)]
279pub struct CompiledExpr {
280 /// The original XPath expression source text
281 pub original: String,
282 /// The parsed expression tree
283 pub expr: Expr,
284}
285
286impl CompiledExpr {
287 /// Create a compiled expression from its source text and parsed AST.
288 pub const fn new(original: String, expr: Expr) -> Self {
289 Self { original, expr }
290 }
291}