1#![allow(dead_code)]
8
9use std::fmt;
10
11use crate::ids::NameId;
12use crate::namespace::context::NamespaceContextSnapshot;
13use crate::namespace::table::NameTable;
14use crate::schema::model::XsdVersion;
15
16use super::identity_lexer::IdXPathLexError;
17
18#[derive(Debug, Clone)]
20pub enum IdentityXPathError {
21 Lex(IdXPathLexError),
23 Parse { message: String, position: usize },
25 UnboundPrefix { prefix: String, position: usize },
27 Restriction { message: String, position: usize },
29}
30
31impl fmt::Display for IdentityXPathError {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match self {
34 IdentityXPathError::Lex(e) => write!(f, "{e}"),
35 IdentityXPathError::Parse { message, position } => {
36 write!(
37 f,
38 "identity XPath parse error at position {position}: {message}"
39 )
40 }
41 IdentityXPathError::UnboundPrefix { prefix, position } => {
42 write!(
43 f,
44 "identity XPath error at position {position}: unbound prefix `{prefix}`"
45 )
46 }
47 IdentityXPathError::Restriction { message, position } => {
48 write!(
49 f,
50 "identity XPath restriction at position {position}: {message}"
51 )
52 }
53 }
54 }
55}
56
57impl std::error::Error for IdentityXPathError {}
58
59impl From<IdXPathLexError> for IdentityXPathError {
60 fn from(e: IdXPathLexError) -> Self {
61 IdentityXPathError::Lex(e)
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub(crate) enum NamespaceMatch {
68 NoNamespace,
70 Exact(NameId),
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub(crate) enum NameTest {
77 Wildcard,
79 NamespaceWildcard(NameId),
81 QName {
83 namespace: NamespaceMatch,
84 local_name: NameId,
85 },
86}
87
88impl NameTest {
89 pub(crate) fn matches(&self, namespace_uri: NameId, local_name: NameId) -> bool {
91 match self {
92 NameTest::Wildcard => true,
93 NameTest::NamespaceWildcard(ns) => namespace_uri == *ns,
94 NameTest::QName {
95 namespace,
96 local_name: ln,
97 } => {
98 *ln == local_name
99 && match namespace {
100 NamespaceMatch::NoNamespace => {
101 namespace_uri.0 == 0
103 }
104 NamespaceMatch::Exact(ns) => namespace_uri == *ns,
105 }
106 }
107 }
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
113pub(crate) enum AstStep {
114 SelfNode,
116 Child(NameTest),
118 Attribute(NameTest),
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
124pub(crate) struct AstPath {
125 pub descendant: bool,
127 pub steps: Vec<AstStep>,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
133pub(crate) struct Asttree {
134 pub paths: Vec<AstPath>,
136}
137
138impl Asttree {
139 pub fn compile_selector(
144 xpath: &str,
145 ns_snapshot: &NamespaceContextSnapshot,
146 name_table: &NameTable,
147 own_xpath_default_ns: Option<&str>,
148 schema_xpath_default_ns: Option<NameId>,
149 target_namespace: Option<NameId>,
150 xsd_version: XsdVersion,
151 ) -> Result<Asttree, IdentityXPathError> {
152 use super::identity_parser::IdXPathParser;
153
154 let (effective_own, effective_schema) = match xsd_version {
156 XsdVersion::V1_0 => (None, None),
157 XsdVersion::V1_1 => (own_xpath_default_ns, schema_xpath_default_ns),
158 };
159
160 let unprefixed_ns = resolve_effective_default_ns(
161 effective_own,
162 effective_schema,
163 ns_snapshot,
164 target_namespace,
165 name_table,
166 );
167 let mut parser = IdXPathParser::new(xpath, ns_snapshot, name_table, unprefixed_ns)?;
168 parser.parse_selector()
169 }
170
171 pub fn compile_field(
176 xpath: &str,
177 ns_snapshot: &NamespaceContextSnapshot,
178 name_table: &NameTable,
179 own_xpath_default_ns: Option<&str>,
180 schema_xpath_default_ns: Option<NameId>,
181 target_namespace: Option<NameId>,
182 xsd_version: XsdVersion,
183 ) -> Result<Asttree, IdentityXPathError> {
184 use super::identity_parser::IdXPathParser;
185
186 let (effective_own, effective_schema) = match xsd_version {
188 XsdVersion::V1_0 => (None, None),
189 XsdVersion::V1_1 => (own_xpath_default_ns, schema_xpath_default_ns),
190 };
191
192 let unprefixed_ns = resolve_effective_default_ns(
193 effective_own,
194 effective_schema,
195 ns_snapshot,
196 target_namespace,
197 name_table,
198 );
199 let mut parser = IdXPathParser::new(xpath, ns_snapshot, name_table, unprefixed_ns)?;
200 parser.parse_field()
201 }
202}
203
204fn resolve_effective_default_ns(
214 own_raw: Option<&str>,
215 schema_raw_id: Option<NameId>,
216 ns_snapshot: &NamespaceContextSnapshot,
217 target_namespace: Option<NameId>,
218 name_table: &NameTable,
219) -> NamespaceMatch {
220 let effective = if let Some(raw) = own_raw {
222 Some(raw.to_string())
223 } else {
224 schema_raw_id.map(|id| name_table.resolve(id))
225 };
226
227 match effective.as_deref() {
228 Some("##defaultNamespace") => match ns_snapshot.default_ns {
229 Some(ns_id) => NamespaceMatch::Exact(ns_id),
230 None => NamespaceMatch::NoNamespace,
231 },
232 Some("##targetNamespace") => match target_namespace {
233 Some(ns_id) => NamespaceMatch::Exact(ns_id),
234 None => NamespaceMatch::NoNamespace,
235 },
236 Some("##local") => NamespaceMatch::NoNamespace,
237 Some(uri) => NamespaceMatch::Exact(name_table.add(uri)),
238 None => NamespaceMatch::NoNamespace,
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use crate::namespace::context::NamespaceContextSnapshot;
246 use crate::namespace::table::NameTable;
247
248 #[test]
251 fn no_default_unprefixed() {
252 let table = NameTable::new();
253 let snapshot = NamespaceContextSnapshot::default();
254 let result = resolve_effective_default_ns(None, None, &snapshot, None, &table);
255 assert_eq!(result, NamespaceMatch::NoNamespace);
256 }
257
258 #[test]
259 fn own_default_namespace() {
260 let table = NameTable::new();
261 let uri_id = table.add("http://example.com/ns");
262 let snapshot = NamespaceContextSnapshot {
263 default_ns: Some(uri_id),
264 bindings: vec![],
265 };
266 let result =
267 resolve_effective_default_ns(Some("##defaultNamespace"), None, &snapshot, None, &table);
268 assert_eq!(result, NamespaceMatch::Exact(uri_id));
269 }
270
271 #[test]
272 fn own_target_namespace() {
273 let table = NameTable::new();
274 let tns = table.add("http://example.com/target");
275 let snapshot = NamespaceContextSnapshot::default();
276 let result = resolve_effective_default_ns(
277 Some("##targetNamespace"),
278 None,
279 &snapshot,
280 Some(tns),
281 &table,
282 );
283 assert_eq!(result, NamespaceMatch::Exact(tns));
284 }
285
286 #[test]
287 fn own_local() {
288 let table = NameTable::new();
289 let snapshot = NamespaceContextSnapshot::default();
290 let result = resolve_effective_default_ns(Some("##local"), None, &snapshot, None, &table);
291 assert_eq!(result, NamespaceMatch::NoNamespace);
292 }
293
294 #[test]
295 fn own_literal_uri() {
296 let table = NameTable::new();
297 let snapshot = NamespaceContextSnapshot::default();
298 let result =
299 resolve_effective_default_ns(Some("http://example.com"), None, &snapshot, None, &table);
300 let expected_id = table.add("http://example.com");
301 assert_eq!(result, NamespaceMatch::Exact(expected_id));
302 }
303
304 #[test]
305 fn cascade_own_over_schema() {
306 let table = NameTable::new();
307 let schema_ns = table.add("http://example.com");
308 let snapshot = NamespaceContextSnapshot::default();
309 let result =
311 resolve_effective_default_ns(Some("##local"), Some(schema_ns), &snapshot, None, &table);
312 assert_eq!(result, NamespaceMatch::NoNamespace);
313 }
314
315 #[test]
316 fn cascade_schema_fallback() {
317 let table = NameTable::new();
318 let schema_ns = table.add("http://example.com");
319 let snapshot = NamespaceContextSnapshot::default();
320 let result = resolve_effective_default_ns(None, Some(schema_ns), &snapshot, None, &table);
322 assert_eq!(result, NamespaceMatch::Exact(schema_ns));
323 }
324
325 #[test]
326 fn default_ns_absent() {
327 let table = NameTable::new();
328 let snapshot = NamespaceContextSnapshot {
329 default_ns: None,
330 bindings: vec![],
331 };
332 let result =
333 resolve_effective_default_ns(Some("##defaultNamespace"), None, &snapshot, None, &table);
334 assert_eq!(result, NamespaceMatch::NoNamespace);
335 }
336
337 #[test]
338 fn target_ns_absent() {
339 let table = NameTable::new();
340 let snapshot = NamespaceContextSnapshot::default();
341 let result =
342 resolve_effective_default_ns(Some("##targetNamespace"), None, &snapshot, None, &table);
343 assert_eq!(result, NamespaceMatch::NoNamespace);
344 }
345
346 #[test]
349 fn wildcard_matches_anything() {
350 let table = NameTable::new();
351 let ns = table.add("http://example.com");
352 let ln = table.add("foo");
353 assert!(NameTest::Wildcard.matches(ns, ln));
354 }
355
356 #[test]
357 fn namespace_wildcard_matches_same_ns() {
358 let table = NameTable::new();
359 let ns = table.add("http://example.com");
360 let ln = table.add("foo");
361 assert!(NameTest::NamespaceWildcard(ns).matches(ns, ln));
362 }
363
364 #[test]
365 fn namespace_wildcard_rejects_different_ns() {
366 let table = NameTable::new();
367 let ns1 = table.add("http://example.com/1");
368 let ns2 = table.add("http://example.com/2");
369 let ln = table.add("foo");
370 assert!(!NameTest::NamespaceWildcard(ns1).matches(ns2, ln));
371 }
372
373 #[test]
374 fn qname_exact_match() {
375 let table = NameTable::new();
376 let ns = table.add("http://example.com");
377 let ln = table.add("foo");
378 let test = NameTest::QName {
379 namespace: NamespaceMatch::Exact(ns),
380 local_name: ln,
381 };
382 assert!(test.matches(ns, ln));
383 }
384
385 #[test]
386 fn qname_no_namespace_match() {
387 let table = NameTable::new();
388 let ln = table.add("foo");
389 let test = NameTest::QName {
390 namespace: NamespaceMatch::NoNamespace,
391 local_name: ln,
392 };
393 use crate::namespace::table::well_known;
394 assert!(test.matches(well_known::EMPTY, ln));
396 }
397
398 #[test]
399 fn qname_rejects_wrong_local() {
400 let table = NameTable::new();
401 let ns = table.add("http://example.com");
402 let ln1 = table.add("foo");
403 let ln2 = table.add("bar");
404 let test = NameTest::QName {
405 namespace: NamespaceMatch::Exact(ns),
406 local_name: ln1,
407 };
408 assert!(!test.matches(ns, ln2));
409 }
410
411 #[test]
414 fn compile_selector_v10_ignores_own_xpath_default_ns() {
415 let table = NameTable::new();
418 let snapshot = NamespaceContextSnapshot::default();
419 let tree = Asttree::compile_selector(
420 "foo",
421 &snapshot,
422 &table,
423 Some("http://example.com/default"),
424 None,
425 None,
426 XsdVersion::V1_0,
427 )
428 .unwrap();
429 match &tree.paths[0].steps[0] {
430 AstStep::Child(NameTest::QName { namespace, .. }) => {
431 assert_eq!(*namespace, NamespaceMatch::NoNamespace);
432 }
433 other => panic!("expected Child(QName{{NoNamespace, ..}}), got {other:?}"),
434 }
435 }
436
437 #[test]
438 fn compile_selector_v11_applies_own_xpath_default_ns() {
439 let table = NameTable::new();
441 let snapshot = NamespaceContextSnapshot::default();
442 let ns_id = table.add("http://example.com/default");
443 let tree = Asttree::compile_selector(
444 "foo",
445 &snapshot,
446 &table,
447 Some("http://example.com/default"),
448 None,
449 None,
450 XsdVersion::V1_1,
451 )
452 .unwrap();
453 match &tree.paths[0].steps[0] {
454 AstStep::Child(NameTest::QName { namespace, .. }) => {
455 assert_eq!(*namespace, NamespaceMatch::Exact(ns_id));
456 }
457 other => panic!("expected Child(QName{{Exact, ..}}), got {other:?}"),
458 }
459 }
460
461 #[test]
462 fn compile_selector_v10_ignores_schema_xpath_default_ns() {
463 let table = NameTable::new();
465 let schema_ns = table.add("http://example.com/schema");
466 let snapshot = NamespaceContextSnapshot::default();
467 let tree = Asttree::compile_selector(
468 "foo",
469 &snapshot,
470 &table,
471 None,
472 Some(schema_ns),
473 None,
474 XsdVersion::V1_0,
475 )
476 .unwrap();
477 match &tree.paths[0].steps[0] {
478 AstStep::Child(NameTest::QName { namespace, .. }) => {
479 assert_eq!(*namespace, NamespaceMatch::NoNamespace);
480 }
481 other => panic!("expected Child(QName{{NoNamespace, ..}}), got {other:?}"),
482 }
483 }
484
485 #[test]
486 fn compile_field_v10_ignores_xpath_default_ns() {
487 let table = NameTable::new();
489 let snapshot = NamespaceContextSnapshot::default();
490 let tree = Asttree::compile_field(
491 "foo",
492 &snapshot,
493 &table,
494 Some("http://example.com/default"),
495 None,
496 None,
497 XsdVersion::V1_0,
498 )
499 .unwrap();
500 match &tree.paths[0].steps[0] {
501 AstStep::Child(NameTest::QName { namespace, .. }) => {
502 assert_eq!(*namespace, NamespaceMatch::NoNamespace);
503 }
504 other => panic!("expected Child(QName{{NoNamespace, ..}}), got {other:?}"),
505 }
506 }
507}