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,
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}