Skip to main content

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}