1#![allow(dead_code)]
16
17use crate::ids::NameId;
18use crate::namespace::context::NamespaceContextSnapshot;
19use crate::namespace::table::NameTable;
20
21use super::asttree::{AstPath, AstStep, Asttree, IdentityXPathError, NameTest, NamespaceMatch};
22use super::identity_lexer::{IdXPathLexer, IdXPathSpanned, IdXPathToken};
23
24pub(crate) struct IdXPathParser<'a> {
26 tokens: Vec<IdXPathSpanned<'a>>,
28 pos: usize,
30 ns_snapshot: &'a NamespaceContextSnapshot,
32 name_table: &'a NameTable,
34 unprefixed_ns: NamespaceMatch,
36}
37
38impl<'a> IdXPathParser<'a> {
39 pub fn new(
41 input: &'a str,
42 ns_snapshot: &'a NamespaceContextSnapshot,
43 name_table: &'a NameTable,
44 unprefixed_ns: NamespaceMatch,
45 ) -> Result<Self, IdentityXPathError> {
46 let tokens: Vec<_> = IdXPathLexer::new(input).collect::<Result<Vec<_>, _>>()?;
47 Ok(Self {
48 tokens,
49 pos: 0,
50 ns_snapshot,
51 name_table,
52 unprefixed_ns,
53 })
54 }
55
56 fn peek(&self) -> Option<&IdXPathToken<'a>> {
60 self.tokens.get(self.pos).map(|(_, tok, _)| tok)
61 }
62
63 fn peek_at(&self, n: usize) -> Option<&IdXPathToken<'a>> {
65 self.tokens.get(self.pos + n).map(|(_, tok, _)| tok)
66 }
67
68 fn current_position(&self) -> usize {
70 self.tokens
71 .get(self.pos)
72 .map(|(start, _, _)| *start)
73 .unwrap_or_else(|| self.tokens.last().map(|(_, _, end)| *end).unwrap_or(0))
74 }
75
76 fn advance(&mut self) -> Option<IdXPathSpanned<'a>> {
78 if self.pos < self.tokens.len() {
79 let tok = self.tokens[self.pos];
80 self.pos += 1;
81 Some(tok)
82 } else {
83 None
84 }
85 }
86
87 fn eat(
89 &mut self,
90 expected: IdXPathToken<'_>,
91 ) -> Result<IdXPathSpanned<'a>, IdentityXPathError> {
92 if self.peek() == Some(&expected) {
93 Ok(self.advance().unwrap())
94 } else {
95 Err(IdentityXPathError::Parse {
96 message: format!("expected `{expected:?}`, found {:?}", self.peek()),
97 position: self.current_position(),
98 })
99 }
100 }
101
102 fn at_end(&self) -> bool {
104 self.pos >= self.tokens.len()
105 }
106
107 fn resolve_prefix(&self, prefix: &str, pos: usize) -> Result<NameId, IdentityXPathError> {
109 let prefix_id = self.name_table.add(prefix);
110 self.ns_snapshot.resolve_prefix(prefix_id).ok_or_else(|| {
111 IdentityXPathError::UnboundPrefix {
112 prefix: prefix.to_string(),
113 position: pos,
114 }
115 })
116 }
117
118 pub fn parse_selector(&mut self) -> Result<Asttree, IdentityXPathError> {
124 if self.at_end() {
125 return Err(IdentityXPathError::Parse {
126 message: "empty selector expression".into(),
127 position: 0,
128 });
129 }
130
131 let mut paths = vec![self.parse_path(false)?];
132
133 while self.peek() == Some(&IdXPathToken::Pipe) {
134 self.advance(); paths.push(self.parse_path(false)?);
136 }
137
138 if !self.at_end() {
139 return Err(IdentityXPathError::Parse {
140 message: format!("unexpected token {:?} after expression", self.peek()),
141 position: self.current_position(),
142 });
143 }
144
145 Ok(Asttree { paths })
146 }
147
148 pub fn parse_field(&mut self) -> Result<Asttree, IdentityXPathError> {
152 if self.at_end() {
153 return Err(IdentityXPathError::Parse {
154 message: "empty field expression".into(),
155 position: 0,
156 });
157 }
158
159 let mut paths = vec![self.parse_path(true)?];
160
161 while self.peek() == Some(&IdXPathToken::Pipe) {
162 self.advance(); paths.push(self.parse_path(true)?);
164 }
165
166 if !self.at_end() {
167 return Err(IdentityXPathError::Parse {
168 message: format!("unexpected token {:?} after expression", self.peek()),
169 position: self.current_position(),
170 });
171 }
172
173 Ok(Asttree { paths })
174 }
175
176 fn parse_path(&mut self, allow_attr: bool) -> Result<AstPath, IdentityXPathError> {
178 let mut descendant = false;
179 let mut steps = Vec::new();
180
181 if self.peek() == Some(&IdXPathToken::Dot) {
183 if self.peek_at(1) == Some(&IdXPathToken::DoubleSlash) {
184 self.advance(); self.advance(); descendant = true;
188 } else if self.peek_at(1) == Some(&IdXPathToken::Slash) {
189 let pos = self.current_position();
191 self.advance(); steps.push(AstStep::SelfNode);
193 if self.peek() == Some(&IdXPathToken::Slash) && self.peek_at(1).is_none() {
196 return Err(IdentityXPathError::Parse {
197 message: "trailing `/` in path".into(),
198 position: self.current_position(),
199 });
200 }
201 _ = pos;
202 } else if self.peek_at(1).is_none() || self.peek_at(1) == Some(&IdXPathToken::Pipe) {
203 self.advance(); steps.push(AstStep::SelfNode);
206 return Ok(AstPath { descendant, steps });
207 } else {
208 return Err(IdentityXPathError::Parse {
209 message: format!("unexpected token {:?} after `.`", self.peek_at(1)),
210 position: self.current_position(),
211 });
212 }
213 }
214
215 if steps.is_empty()
217 && (self.peek() == Some(&IdXPathToken::Slash)
218 || self.peek() == Some(&IdXPathToken::DoubleSlash))
219 {
220 return Err(IdentityXPathError::Parse {
221 message: "absolute paths are not allowed in identity-constraint XPath".into(),
222 position: self.current_position(),
223 });
224 }
225
226 if steps.is_empty() {
228 steps.push(self.parse_step(allow_attr)?);
229 }
230
231 while self.peek() == Some(&IdXPathToken::Slash) {
233 if self.peek_at(1).is_none() {
235 return Err(IdentityXPathError::Parse {
236 message: "trailing `/` in path".into(),
237 position: self.current_position(),
238 });
239 }
240 self.advance(); steps.push(self.parse_step(allow_attr)?);
242 }
243
244 for (i, step) in steps.iter().enumerate() {
246 if matches!(step, AstStep::Attribute(_)) && i < steps.len() - 1 {
247 return Err(IdentityXPathError::Restriction {
248 message: "attribute step must be the last step in a path".into(),
249 position: self.current_position(),
250 });
251 }
252 }
253
254 Ok(AstPath { descendant, steps })
255 }
256
257 fn parse_step(&mut self, allow_attr: bool) -> Result<AstStep, IdentityXPathError> {
259 let pos = self.current_position();
260
261 match self.peek() {
262 Some(IdXPathToken::Dot) => {
263 self.advance();
264 Ok(AstStep::SelfNode)
265 }
266 Some(IdXPathToken::At) => {
267 if !allow_attr {
268 return Err(IdentityXPathError::Restriction {
269 message: "attribute axis is not allowed in selector expressions".into(),
270 position: pos,
271 });
272 }
273 self.advance(); let name_test = self.parse_name_test(true)?;
275 Ok(AstStep::Attribute(name_test))
276 }
277 Some(IdXPathToken::NCName(_)) => {
278 if self.peek_at(1) == Some(&IdXPathToken::DoubleColon) {
280 self.parse_explicit_axis(allow_attr)
281 } else {
282 let name_test = self.parse_name_test(false)?;
283 Ok(AstStep::Child(name_test))
284 }
285 }
286 Some(IdXPathToken::Star) => {
287 let name_test = self.parse_name_test(false)?;
288 Ok(AstStep::Child(name_test))
289 }
290 Some(tok) => Err(IdentityXPathError::Parse {
291 message: format!("unexpected token `{tok:?}` at start of step"),
292 position: pos,
293 }),
294 None => Err(IdentityXPathError::Parse {
295 message: "unexpected end of expression".into(),
296 position: pos,
297 }),
298 }
299 }
300
301 fn parse_explicit_axis(&mut self, allow_attr: bool) -> Result<AstStep, IdentityXPathError> {
303 let (pos, tok, _) = self.advance().unwrap(); let axis_name = match tok {
305 IdXPathToken::NCName(name) => name,
306 _ => unreachable!(),
307 };
308 self.advance(); match axis_name {
311 "child" => {
312 let name_test = self.parse_name_test(false)?;
313 Ok(AstStep::Child(name_test))
314 }
315 "attribute" => {
316 if !allow_attr {
317 return Err(IdentityXPathError::Restriction {
318 message: "attribute axis is not allowed in selector expressions".into(),
319 position: pos,
320 });
321 }
322 let name_test = self.parse_name_test(true)?;
323 Ok(AstStep::Attribute(name_test))
324 }
325 other => Err(IdentityXPathError::Parse {
326 message: format!(
327 "unsupported axis `{other}` in identity-constraint XPath \
328 (only `child` and `attribute` are allowed)"
329 ),
330 position: pos,
331 }),
332 }
333 }
334
335 fn parse_name_test(&mut self, is_attribute: bool) -> Result<NameTest, IdentityXPathError> {
340 let pos = self.current_position();
341
342 match self.peek() {
343 Some(IdXPathToken::Star) => {
344 self.advance();
345 Ok(NameTest::Wildcard)
346 }
347 Some(IdXPathToken::NCName(_)) => {
348 let (start, tok, ncname_end) = self.advance().unwrap();
349 let first_name = match tok {
350 IdXPathToken::NCName(n) => n,
351 _ => unreachable!(),
352 };
353
354 if self.peek() == Some(&IdXPathToken::Colon) {
357 let (colon_start, _, colon_end) = self.tokens[self.pos];
358 if colon_start != ncname_end {
359 return Err(IdentityXPathError::Parse {
360 message: "whitespace is not allowed between namespace prefix and `:`"
361 .into(),
362 position: ncname_end,
363 });
364 }
365 self.advance(); match self.peek() {
368 Some(IdXPathToken::Star) => {
369 let (star_start, _, _) = self.tokens[self.pos];
371 if star_start != colon_end {
372 return Err(IdentityXPathError::Parse {
373 message: "whitespace is not allowed between `:` and `*`".into(),
374 position: colon_end,
375 });
376 }
377 self.advance();
378 let ns_id = self.resolve_prefix(first_name, start)?;
379 Ok(NameTest::NamespaceWildcard(ns_id))
380 }
381 Some(IdXPathToken::NCName(_)) => {
382 let (local_start, _, _) = self.tokens[self.pos];
384 if local_start != colon_end {
385 return Err(IdentityXPathError::Parse {
386 message: "whitespace is not allowed between `:` and local name"
387 .into(),
388 position: colon_end,
389 });
390 }
391 let (_, tok2, _) = self.advance().unwrap();
392 let local = match tok2 {
393 IdXPathToken::NCName(n) => n,
394 _ => unreachable!(),
395 };
396 let ns_id = self.resolve_prefix(first_name, start)?;
397 let local_id = self.name_table.add(local);
398 Ok(NameTest::QName {
399 namespace: NamespaceMatch::Exact(ns_id),
400 local_name: local_id,
401 })
402 }
403 _ => Err(IdentityXPathError::Parse {
404 message: "expected name or `*` after `:`".into(),
405 position: self.current_position(),
406 }),
407 }
408 } else {
409 let local_id = self.name_table.add(first_name);
412 let ns = if is_attribute {
413 NamespaceMatch::NoNamespace
414 } else {
415 self.unprefixed_ns
416 };
417 Ok(NameTest::QName {
418 namespace: ns,
419 local_name: local_id,
420 })
421 }
422 }
423 _ => Err(IdentityXPathError::Parse {
424 message: format!("expected name or `*`, found {:?}", self.peek()),
425 position: pos,
426 }),
427 }
428 }
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434 use crate::namespace::context::NamespaceContextSnapshot;
435 use crate::namespace::table::NameTable;
436
437 fn snapshot_with_prefix(
439 table: &NameTable,
440 prefix: &str,
441 uri: &str,
442 ) -> NamespaceContextSnapshot {
443 let prefix_id = table.add(prefix);
444 let uri_id = table.add(uri);
445 NamespaceContextSnapshot {
446 default_ns: None,
447 bindings: vec![(prefix_id, uri_id)],
448 }
449 }
450
451 fn compile_selector(input: &str) -> Result<Asttree, IdentityXPathError> {
453 use crate::schema::model::XsdVersion;
454 let table = NameTable::new();
455 let snapshot = NamespaceContextSnapshot::default();
456 Asttree::compile_selector(input, &snapshot, &table, None, None, None, XsdVersion::V1_1)
457 }
458
459 fn compile_field(input: &str) -> Result<Asttree, IdentityXPathError> {
461 use crate::schema::model::XsdVersion;
462 let table = NameTable::new();
463 let snapshot = NamespaceContextSnapshot::default();
464 Asttree::compile_field(input, &snapshot, &table, None, None, None, XsdVersion::V1_1)
465 }
466
467 #[test]
470 fn simple_child() {
471 let tree = compile_selector("foo").unwrap();
472 assert_eq!(tree.paths.len(), 1);
473 let path = &tree.paths[0];
474 assert!(!path.descendant);
475 assert_eq!(path.steps.len(), 1);
476 match &path.steps[0] {
477 AstStep::Child(NameTest::QName { namespace, .. }) => {
478 assert_eq!(*namespace, NamespaceMatch::NoNamespace);
479 }
480 other => panic!("expected Child(QName), got {other:?}"),
481 }
482 }
483
484 #[test]
485 fn multi_step() {
486 let tree = compile_selector("foo/bar").unwrap();
487 assert_eq!(tree.paths.len(), 1);
488 assert_eq!(tree.paths[0].steps.len(), 2);
489 assert!(matches!(&tree.paths[0].steps[0], AstStep::Child(_)));
490 assert!(matches!(&tree.paths[0].steps[1], AstStep::Child(_)));
491 }
492
493 #[test]
494 fn descendant_prefix() {
495 let tree = compile_selector(".//foo").unwrap();
496 assert_eq!(tree.paths.len(), 1);
497 assert!(tree.paths[0].descendant);
498 assert_eq!(tree.paths[0].steps.len(), 1);
499 }
500
501 #[test]
502 fn self_then_child() {
503 let tree = compile_selector("./foo").unwrap();
504 assert_eq!(tree.paths.len(), 1);
505 let path = &tree.paths[0];
506 assert!(!path.descendant);
507 assert_eq!(path.steps.len(), 2);
508 assert_eq!(path.steps[0], AstStep::SelfNode);
509 assert!(matches!(&path.steps[1], AstStep::Child(_)));
510 }
511
512 #[test]
513 fn union() {
514 let tree = compile_selector("a|b|c").unwrap();
515 assert_eq!(tree.paths.len(), 3);
516 }
517
518 #[test]
519 fn wildcard() {
520 let tree = compile_selector("*").unwrap();
521 assert_eq!(tree.paths[0].steps.len(), 1);
522 assert!(matches!(
523 &tree.paths[0].steps[0],
524 AstStep::Child(NameTest::Wildcard)
525 ));
526 }
527
528 #[test]
529 fn ns_wildcard() {
530 use crate::schema::model::XsdVersion;
531 let table = NameTable::new();
532 let snapshot = snapshot_with_prefix(&table, "ns", "http://example.com");
533 let tree = Asttree::compile_selector(
534 "ns:*",
535 &snapshot,
536 &table,
537 None,
538 None,
539 None,
540 XsdVersion::V1_1,
541 )
542 .unwrap();
543 assert!(matches!(
544 &tree.paths[0].steps[0],
545 AstStep::Child(NameTest::NamespaceWildcard(_))
546 ));
547 }
548
549 #[test]
550 fn prefixed_qname() {
551 use crate::schema::model::XsdVersion;
552 let table = NameTable::new();
553 let snapshot = snapshot_with_prefix(&table, "ns", "http://example.com");
554 let ns_id = table.add("http://example.com");
555 let tree = Asttree::compile_selector(
556 "ns:foo",
557 &snapshot,
558 &table,
559 None,
560 None,
561 None,
562 XsdVersion::V1_1,
563 )
564 .unwrap();
565 match &tree.paths[0].steps[0] {
566 AstStep::Child(NameTest::QName {
567 namespace: NamespaceMatch::Exact(ns),
568 local_name,
569 }) => {
570 assert_eq!(*ns, ns_id);
571 assert_eq!(table.resolve(*local_name), "foo");
572 }
573 other => panic!("expected Child(QName{{Exact, foo}}), got {other:?}"),
574 }
575 }
576
577 #[test]
578 fn explicit_child_axis() {
579 let tree = compile_selector("child::foo").unwrap();
580 assert_eq!(tree.paths[0].steps.len(), 1);
581 assert!(matches!(&tree.paths[0].steps[0], AstStep::Child(_)));
582 }
583
584 #[test]
585 fn explicit_attr_field() {
586 let tree = compile_field("attribute::bar").unwrap();
587 assert_eq!(tree.paths[0].steps.len(), 1);
588 assert!(matches!(&tree.paths[0].steps[0], AstStep::Attribute(_)));
589 }
590
591 #[test]
592 fn attr_shorthand() {
593 let tree = compile_field("@bar").unwrap();
594 assert_eq!(tree.paths[0].steps.len(), 1);
595 assert!(matches!(&tree.paths[0].steps[0], AstStep::Attribute(_)));
596 }
597
598 #[test]
599 fn field_path_with_attr() {
600 let tree = compile_field("foo/@bar").unwrap();
601 assert_eq!(tree.paths[0].steps.len(), 2);
602 assert!(matches!(&tree.paths[0].steps[0], AstStep::Child(_)));
603 assert!(matches!(&tree.paths[0].steps[1], AstStep::Attribute(_)));
604 }
605
606 #[test]
607 fn complex_field() {
608 let tree = compile_field(".//a/b/@c").unwrap();
609 let path = &tree.paths[0];
610 assert!(path.descendant);
611 assert_eq!(path.steps.len(), 3);
612 assert!(matches!(&path.steps[0], AstStep::Child(_)));
613 assert!(matches!(&path.steps[1], AstStep::Child(_)));
614 assert!(matches!(&path.steps[2], AstStep::Attribute(_)));
615 }
616
617 #[test]
620 fn reject_attr_in_selector() {
621 let err = compile_selector("@foo").unwrap_err();
622 assert!(matches!(err, IdentityXPathError::Restriction { .. }));
623 }
624
625 #[test]
626 fn reject_attr_axis_in_selector() {
627 let err = compile_selector("attribute::foo").unwrap_err();
628 assert!(matches!(err, IdentityXPathError::Restriction { .. }));
629 }
630
631 #[test]
632 fn reject_attr_not_last() {
633 let err = compile_field("@foo/bar").unwrap_err();
634 assert!(matches!(err, IdentityXPathError::Restriction { .. }));
635 }
636
637 #[test]
638 fn reject_unsupported_axis() {
639 let err = compile_selector("parent::foo").unwrap_err();
640 assert!(matches!(err, IdentityXPathError::Parse { .. }));
641 }
642
643 #[test]
644 fn reject_absolute_path() {
645 let err = compile_selector("/foo").unwrap_err();
646 assert!(matches!(err, IdentityXPathError::Parse { .. }));
647 }
648
649 #[test]
650 fn reject_empty() {
651 let err = compile_selector("").unwrap_err();
652 assert!(matches!(err, IdentityXPathError::Parse { .. }));
653 }
654
655 #[test]
656 fn reject_trailing_slash() {
657 let err = compile_selector("foo/").unwrap_err();
658 assert!(matches!(err, IdentityXPathError::Parse { .. }));
659 }
660
661 #[test]
662 fn reject_unbound_prefix() {
663 let err = compile_selector("unknown:foo").unwrap_err();
664 assert!(matches!(err, IdentityXPathError::UnboundPrefix { .. }));
665 }
666
667 #[test]
668 fn reject_predicate() {
669 let err = compile_selector("foo[1]").unwrap_err();
670 assert!(matches!(err, IdentityXPathError::Lex(_)));
671 }
672
673 #[test]
674 fn reject_parent_axis() {
675 let err = compile_selector("foo/..").unwrap_err();
676 assert!(matches!(err, IdentityXPathError::Lex(_)));
677 }
678
679 #[test]
682 fn unprefixed_attr_ignores_xpath_default_ns() {
683 use crate::schema::model::XsdVersion;
686 let table = NameTable::new();
687 let snapshot = NamespaceContextSnapshot::default();
688 let tree = Asttree::compile_field(
689 "@id",
690 &snapshot,
691 &table,
692 Some("http://example.com/default"),
693 None,
694 None,
695 XsdVersion::V1_1,
696 )
697 .unwrap();
698 match &tree.paths[0].steps[0] {
699 AstStep::Attribute(NameTest::QName { namespace, .. }) => {
700 assert_eq!(*namespace, NamespaceMatch::NoNamespace);
701 }
702 other => panic!("expected Attribute(QName{{NoNamespace, ..}}), got {other:?}"),
703 }
704 }
705
706 #[test]
707 fn unprefixed_attr_via_explicit_axis_ignores_xpath_default_ns() {
708 use crate::schema::model::XsdVersion;
709 let table = NameTable::new();
710 let snapshot = NamespaceContextSnapshot::default();
711 let tree = Asttree::compile_field(
712 "attribute::id",
713 &snapshot,
714 &table,
715 Some("http://example.com/default"),
716 None,
717 None,
718 XsdVersion::V1_1,
719 )
720 .unwrap();
721 match &tree.paths[0].steps[0] {
722 AstStep::Attribute(NameTest::QName { namespace, .. }) => {
723 assert_eq!(*namespace, NamespaceMatch::NoNamespace);
724 }
725 other => panic!("expected Attribute(QName{{NoNamespace, ..}}), got {other:?}"),
726 }
727 }
728
729 #[test]
730 fn unprefixed_child_uses_xpath_default_ns() {
731 use crate::schema::model::XsdVersion;
733 let table = NameTable::new();
734 let snapshot = NamespaceContextSnapshot::default();
735 let ns_id = table.add("http://example.com/default");
736 let tree = Asttree::compile_selector(
737 "foo",
738 &snapshot,
739 &table,
740 Some("http://example.com/default"),
741 None,
742 None,
743 XsdVersion::V1_1,
744 )
745 .unwrap();
746 match &tree.paths[0].steps[0] {
747 AstStep::Child(NameTest::QName { namespace, .. }) => {
748 assert_eq!(*namespace, NamespaceMatch::Exact(ns_id));
749 }
750 other => panic!("expected Child(QName{{Exact, ..}}), got {other:?}"),
751 }
752 }
753}