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,
24 AncestorOrSelf,
25 Attribute,
26 Child,
27 Descendant,
28 DescendantOrSelf,
29 Following,
30 FollowingSibling,
31 Namespace,
32 Parent,
33 Preceding,
34 PrecedingSibling,
35 Self_,
36}
37
38impl Axis {
39 /// All 13 XPath 1.0 axes.
40 pub const ALL: &'static [Axis] = &[
41 Axis::Ancestor,
42 Axis::AncestorOrSelf,
43 Axis::Attribute,
44 Axis::Child,
45 Axis::Descendant,
46 Axis::DescendantOrSelf,
47 Axis::Following,
48 Axis::FollowingSibling,
49 Axis::Namespace,
50 Axis::Parent,
51 Axis::Preceding,
52 Axis::PrecedingSibling,
53 Axis::Self_,
54 ];
55
56 pub fn as_str(&self) -> &'static str {
57 match self {
58 Axis::Ancestor => "ancestor",
59 Axis::AncestorOrSelf => "ancestor-or-self",
60 Axis::Attribute => "attribute",
61 Axis::Child => "child",
62 Axis::Descendant => "descendant",
63 Axis::DescendantOrSelf => "descendant-or-self",
64 Axis::Following => "following",
65 Axis::FollowingSibling => "following-sibling",
66 Axis::Namespace => "namespace",
67 Axis::Parent => "parent",
68 Axis::Preceding => "preceding",
69 Axis::PrecedingSibling => "preceding-sibling",
70 Axis::Self_ => "self",
71 }
72 }
73}
74
75impl fmt::Display for Axis {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 write!(f, "{}", self.as_str())
78 }
79}
80
81// ═══════════════════════════════════════════════════════════════════════════════
82// Node Tests
83// ═══════════════════════════════════════════════════════════════════════════════
84
85/// XPath node test.
86#[derive(Debug, Clone, PartialEq)]
87pub enum NodeTest {
88 /// name() — matches any node of principal node type
89 NameTest(NameTest),
90 /// comment()
91 Comment,
92 /// text()
93 Text,
94 /// processing-instruction() or processing-instruction("target")
95 ProcessingInstruction(Option<String>),
96 /// node()
97 Node,
98 /// * — matches any node of principal node type
99 Wildcard,
100 /// prefix:* — namespace wildcard
101 NsWildcard(String),
102}
103
104/// A name test (QName or wildcard).
105#[derive(Debug, Clone, PartialEq)]
106pub enum NameTest {
107 /// Just a local name: "para"
108 LocalName(String),
109 /// Qualified name: "xslt:template"
110 QName { prefix: String, local: String },
111 /// Wildcard name: *
112 Any,
113}
114
115impl fmt::Display for NodeTest {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 match self {
118 NodeTest::NameTest(n) => match n {
119 NameTest::LocalName(s) => write!(f, "{}", s),
120 NameTest::QName { prefix, local } => write!(f, "{}:{}", prefix, local),
121 NameTest::Any => write!(f, "*"),
122 },
123 NodeTest::Comment => write!(f, "comment()"),
124 NodeTest::Text => write!(f, "text()"),
125 NodeTest::ProcessingInstruction(None) => write!(f, "processing-instruction()"),
126 NodeTest::ProcessingInstruction(Some(t)) => {
127 write!(f, "processing-instruction('{}')", t)
128 }
129 NodeTest::Node => write!(f, "node()"),
130 NodeTest::Wildcard => write!(f, "*"),
131 NodeTest::NsWildcard(prefix) => write!(f, "{}:*", prefix),
132 }
133 }
134}
135
136// ═══════════════════════════════════════════════════════════════════════════════
137// Step
138// ═══════════════════════════════════════════════════════════════════════════════
139
140/// A single location step: axis::node-test[predicates]
141#[derive(Debug, Clone, PartialEq)]
142pub struct Step {
143 pub axis: Axis,
144 pub node_test: NodeTest,
145 pub predicates: Vec<Expr>,
146}
147
148// ═══════════════════════════════════════════════════════════════════════════════
149// Binary Operators
150// ═══════════════════════════════════════════════════════════════════════════════
151
152#[derive(Debug, Clone, Copy, PartialEq)]
153pub enum BinaryOp {
154 /// `|` — union
155 Union,
156 /// `and`
157 And,
158 /// `or`
159 Or,
160 /// `=`
161 Eq,
162 /// `!=`
163 Ne,
164 /// `<`
165 Lt,
166 /// `>`
167 Gt,
168 /// `<=`
169 Le,
170 /// `>=`
171 Ge,
172 /// `+`
173 Add,
174 /// `-`
175 Sub,
176 /// `*` (multiplication)
177 Mul,
178 /// `div`
179 Div,
180 /// `mod`
181 Mod,
182}
183
184// ═══════════════════════════════════════════════════════════════════════════════
185// Expressions
186// ═══════════════════════════════════════════════════════════════════════════════
187
188/// XPath expression AST node.
189#[derive(Debug, Clone, PartialEq)]
190pub enum Expr {
191 /// Absolute location path: `/expr`
192 AbsolutePath(Box<Expr>),
193 /// Relative location path: `step1/step2`
194 RelativePath(Box<Expr>, Box<Expr>),
195 /// A single step
196 Step(Step),
197 /// Filter expression: `primary[pred1][pred2]`
198 Filter(Box<Expr>, Vec<Expr>),
199 /// Variable reference: `$name`
200 Variable(String),
201 /// String literal: `'hello'`
202 StringLiteral(String),
203 /// Numeric literal: `42` or `3.14`
204 NumberLiteral(f64),
205 /// Boolean literal
206 BooleanLiteral(bool),
207 /// Function call: `name(arg1, arg2)`
208 FunctionCall { name: String, args: Vec<Expr> },
209 /// Binary operation: `left op right`
210 BinaryOp {
211 op: BinaryOp,
212 left: Box<Expr>,
213 right: Box<Expr>,
214 },
215 /// Unary minus: `-expr`
216 UnaryMinus(Box<Expr>),
217 /// Union expression: `left | right`
218 Union(Box<Expr>, Box<Expr>),
219}
220
221impl Expr {
222 /// Check if this expression is a location path (returns nodeset).
223 pub fn is_location_path(&self) -> bool {
224 matches!(
225 self,
226 Expr::AbsolutePath(_) | Expr::RelativePath(_, _) | Expr::Step(_) | Expr::Filter(_, _)
227 )
228 }
229
230 /// Check if this is a constant value (no evaluation needed).
231 pub fn is_constant(&self) -> bool {
232 matches!(
233 self,
234 Expr::StringLiteral(_) | Expr::NumberLiteral(_) | Expr::BooleanLiteral(_)
235 )
236 }
237}
238
239// ═══════════════════════════════════════════════════════════════════════════════
240// Compiled Expression
241// ═══════════════════════════════════════════════════════════════════════════════
242
243/// A compiled XPath expression.
244///
245/// Internal representation, not the C ABI `xmlXPathCompExprPtr`.
246#[derive(Debug, Clone)]
247pub struct CompiledExpr {
248 pub original: String,
249 pub expr: Expr,
250}
251
252impl CompiledExpr {
253 pub fn new(original: String, expr: Expr) -> Self {
254 Self { original, expr }
255 }
256}