xsd_schema/xpath/ast/paths.rs
1// ============================================================================
2// Path Expressions
3// ============================================================================
4
5use super::{AstNodeId, SourceSpan};
6
7/// XPath axis specifier.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Axis {
10 /// `child::` (default for element names)
11 Child,
12 /// `descendant::`
13 Descendant,
14 /// `attribute::` (abbreviated `@`)
15 Attribute,
16 /// `self::`
17 SelfAxis,
18 /// `descendant-or-self::`
19 DescendantOrSelf,
20 /// `following-sibling::`
21 FollowingSibling,
22 /// `following::`
23 Following,
24 /// `parent::` (abbreviated `..`)
25 Parent,
26 /// `ancestor::`
27 Ancestor,
28 /// `preceding-sibling::`
29 PrecedingSibling,
30 /// `preceding::`
31 Preceding,
32 /// `ancestor-or-self::`
33 AncestorOrSelf,
34 /// `namespace::`
35 Namespace,
36}
37
38impl Axis {
39 /// Check if this is a reverse axis (traverses in reverse document order).
40 pub fn is_reverse(&self) -> bool {
41 matches!(
42 self,
43 Axis::Parent
44 | Axis::Ancestor
45 | Axis::PrecedingSibling
46 | Axis::Preceding
47 | Axis::AncestorOrSelf
48 )
49 }
50
51 /// Check if this is a forward axis.
52 pub fn is_forward(&self) -> bool {
53 !self.is_reverse()
54 }
55}
56
57/// Node test in a path step.
58#[derive(Debug, Clone)]
59pub enum NodeTest {
60 /// Name test (QName with optional wildcards).
61 Name(NameTest),
62 /// Kind test (`node()`, `element()`, etc.).
63 Kind(KindTest),
64}
65
66/// Name test with optional wildcards.
67#[derive(Debug, Clone)]
68pub struct NameTest {
69 /// Namespace prefix (None = wildcard `*:local`, Some("") = no prefix).
70 pub prefix: Option<String>,
71 /// Local name (None = wildcard `prefix:*` or `*`).
72 pub local_name: Option<String>,
73}
74
75impl NameTest {
76 /// Match any node: `*`.
77 pub fn any() -> Self {
78 Self {
79 prefix: None,
80 local_name: None,
81 }
82 }
83
84 /// Match any local name in a namespace: `prefix:*`.
85 pub fn any_in_ns(prefix: String) -> Self {
86 Self {
87 prefix: Some(prefix),
88 local_name: None,
89 }
90 }
91
92 /// Match any namespace with a specific local name: `*:local`.
93 pub fn any_ns(local_name: String) -> Self {
94 Self {
95 prefix: None,
96 local_name: Some(local_name),
97 }
98 }
99
100 /// Match a specific QName.
101 pub fn qname(prefix: String, local_name: String) -> Self {
102 Self {
103 prefix: Some(prefix),
104 local_name: Some(local_name),
105 }
106 }
107}
108
109/// Kind test (`node()`, `text()`, `element()`, etc.).
110#[derive(Debug, Clone)]
111pub enum KindTest {
112 /// `node()` - matches any node.
113 AnyKind,
114 /// `text()` - matches text nodes.
115 Text,
116 /// `comment()` - matches comment nodes.
117 Comment,
118 /// `processing-instruction()` or `processing-instruction('name')`.
119 ProcessingInstruction(Option<String>),
120 /// `document-node()` or `document-node(element(...))`.
121 Document(Option<Box<KindTest>>),
122 /// `element()` or `element(name)` or `element(name, type)`.
123 Element(ElementTest),
124 /// `attribute()` or `attribute(name)` or `attribute(name, type)`.
125 Attribute(AttributeTest),
126 /// `schema-element(name)`.
127 SchemaElement(String),
128 /// `schema-attribute(name)`.
129 SchemaAttribute(String),
130}
131
132/// Element test: `element()`, `element(name)`, or `element(name, type)`.
133#[derive(Debug, Clone, Default)]
134pub struct ElementTest {
135 /// Element name (None = wildcard).
136 pub name: Option<QName>,
137 /// Type annotation (None = any type).
138 pub type_name: Option<QName>,
139 /// Whether the type allows nilled elements.
140 pub nillable: bool,
141}
142
143/// Attribute test: `attribute()`, `attribute(name)`, or `attribute(name, type)`.
144#[derive(Debug, Clone, Default)]
145pub struct AttributeTest {
146 /// Attribute name (None = wildcard).
147 pub name: Option<QName>,
148 /// Type annotation (None = any type).
149 pub type_name: Option<QName>,
150}
151
152/// Qualified name (prefix:local or just local).
153#[derive(Debug, Clone)]
154pub struct QName {
155 /// Namespace prefix (empty string if none).
156 pub prefix: String,
157 /// Local part.
158 pub local: String,
159}
160
161impl QName {
162 pub fn new(prefix: String, local: String) -> Self {
163 Self { prefix, local }
164 }
165
166 pub fn local_only(local: String) -> Self {
167 Self {
168 prefix: String::new(),
169 local,
170 }
171 }
172}
173
174/// Single step in a path expression.
175#[derive(Debug, Clone)]
176pub struct PathStepNode {
177 /// Axis specifier.
178 pub axis: Axis,
179 /// Node test (AST form with raw strings).
180 pub test: NodeTest,
181 /// Predicates (expression IDs).
182 pub predicates: Vec<AstNodeId>,
183 /// Source location.
184 pub span: SourceSpan,
185 /// Resolved name test (populated during binding).
186 /// Uses interned NameIds and resolved namespace URIs.
187 pub resolved_test: Option<crate::types::NameTest>,
188}
189
190impl PathStepNode {
191 pub fn new(axis: Axis, test: NodeTest, span: SourceSpan) -> Self {
192 Self {
193 axis,
194 test,
195 predicates: Vec::new(),
196 span,
197 resolved_test: None,
198 }
199 }
200
201 pub fn with_predicates(
202 axis: Axis,
203 test: NodeTest,
204 predicates: Vec<AstNodeId>,
205 span: SourceSpan,
206 ) -> Self {
207 Self {
208 axis,
209 test,
210 predicates,
211 span,
212 resolved_test: None,
213 }
214 }
215
216 /// Abbreviated parent step (`..`).
217 pub fn abbrev_parent(span: SourceSpan) -> Self {
218 Self {
219 axis: Axis::Parent,
220 test: NodeTest::Kind(KindTest::AnyKind),
221 predicates: Vec::new(),
222 span,
223 resolved_test: None,
224 }
225 }
226}
227
228/// Path expression (sequence of steps).
229#[derive(Debug, Clone)]
230pub struct PathExprNode {
231 /// Whether the path starts from root (`/`).
232 pub is_absolute: bool,
233 /// Steps in the path (IDs of PathStep nodes or filter expressions).
234 pub steps: Vec<AstNodeId>,
235 /// Source location.
236 pub span: SourceSpan,
237 /// Hint that result order doesn't matter (optimization).
238 pub unordered_hint: bool,
239}
240
241impl PathExprNode {
242 /// Root-only path (`/`).
243 pub fn root_only(span: SourceSpan) -> Self {
244 Self {
245 is_absolute: true,
246 steps: Vec::new(),
247 span,
248 unordered_hint: false,
249 }
250 }
251
252 /// Absolute path (`/a/b`).
253 pub fn absolute(steps: Vec<AstNodeId>, span: SourceSpan) -> Self {
254 Self {
255 is_absolute: true,
256 steps,
257 span,
258 unordered_hint: false,
259 }
260 }
261
262 /// Relative path (`a/b`).
263 pub fn relative(steps: Vec<AstNodeId>, span: SourceSpan) -> Self {
264 Self {
265 is_absolute: false,
266 steps,
267 span,
268 unordered_hint: false,
269 }
270 }
271}
272
273/// Filter expression (`primary[predicate][predicate]...`).
274#[derive(Debug, Clone)]
275pub struct FilterExprNode {
276 /// Base/primary expression.
277 pub base: AstNodeId,
278 /// Predicate expressions.
279 pub predicates: Vec<AstNodeId>,
280 /// Source location.
281 pub span: SourceSpan,
282}
283
284impl FilterExprNode {
285 pub fn new(base: AstNodeId, predicates: Vec<AstNodeId>, span: SourceSpan) -> Self {
286 Self {
287 base,
288 predicates,
289 span,
290 }
291 }
292}