1use sup_xml_tree::dom::{Node, NodeKind};
43
44#[derive(Debug, Clone)]
47pub struct Pattern {
48 branches: Vec<Branch>,
51}
52
53#[derive(Debug, Clone)]
54struct Branch {
55 steps: Vec<Step>,
59 links: Vec<Link>,
62 absolute: bool,
66}
67
68#[derive(Debug, Clone)]
69struct Step {
70 test: Test,
71 predicates: Vec<Predicate>,
72}
73
74#[derive(Debug, Clone)]
75enum Test {
76 Element(String),
78 AnyElement,
80 Attribute(String),
82 AnyAttribute,
84}
85
86#[derive(Debug, Clone)]
87enum Predicate {
88 Position(usize),
90 Last,
92 AttrExists(String),
94 AttrEquals(String, String),
96}
97
98#[derive(Debug, Clone, Copy)]
99enum Link {
100 Parent,
102 Ancestor,
104}
105
106impl Pattern {
107 pub fn compile(src: &str) -> Result<Self, String> {
112 let mut p = Parser::new(src);
113 let pat = p.parse_pattern()?;
114 p.skip_ws();
115 if !p.at_eof() {
116 return Err(format!("unexpected trailing input near {:?}", &p.src[p.pos..]));
117 }
118 Ok(pat)
119 }
120
121 pub fn matches(&self, node: &Node<'_>) -> bool {
124 self.branches.iter().any(|b| b.matches(node))
125 }
126
127 pub fn matches_attribute(&self, attr_name: &str, parent: Option<&Node<'_>>) -> bool {
136 self.branches.iter().any(|b| b.matches_attribute(attr_name, parent))
137 }
138}
139
140impl Branch {
143 fn matches(&self, node: &Node<'_>) -> bool {
144 self.run_from(0, node)
145 }
146
147 fn matches_attribute(&self, attr_name: &str, parent: Option<&Node<'_>>) -> bool {
151 let Some(step0) = self.steps.first() else { return false; };
152 let test_ok = match &step0.test {
153 Test::Attribute(name) => name.as_str() == attr_name,
154 Test::AnyAttribute => true,
155 _ => false,
156 };
157 if !test_ok {
158 return false;
159 }
160 if !step0.predicates.is_empty() {
164 return false;
165 }
166 match self.links.first() {
167 None => !self.absolute,
170 Some(Link::Parent) => match parent {
172 Some(p) => self.run_from(1, p),
173 None => false,
174 },
175 Some(Link::Ancestor) => {
177 let (Some(next_step), Some(p)) = (self.steps.get(1), parent) else {
178 return false;
179 };
180 let mut up = Some(p);
181 while let Some(anc) = up {
182 if next_step.test.matches(anc) && predicates_hold(&next_step.predicates, anc) {
183 return self.run_from(1, anc);
184 }
185 up = anc.parent.get();
186 }
187 false
188 }
189 }
190 }
191
192 fn run_from(&self, start: usize, start_cursor: &Node<'_>) -> bool {
196 let mut cursor = start_cursor;
197
198 for i in start..self.steps.len() {
199 let step = &self.steps[i];
200 if !step.test.matches(cursor) {
202 return false;
203 }
204 if !predicates_hold(&step.predicates, cursor) {
205 return false;
206 }
207
208 let Some(link) = self.links.get(i) else { break; };
210
211 cursor = match link {
212 Link::Parent => match cursor.parent.get() {
213 Some(p) => p,
214 None => return false,
215 },
216 Link::Ancestor => {
217 let Some(next_step) = self.steps.get(i + 1) else {
221 return false;
222 };
223 let mut found = None;
224 let mut up = cursor.parent.get();
225 while let Some(anc) = up {
226 if next_step.test.matches(anc) && predicates_hold(&next_step.predicates, anc) {
227 found = Some(anc);
228 break;
229 }
230 up = anc.parent.get();
231 }
232 match found {
233 Some(a) => {
234 a
243 }
244 None => return false,
245 }
246 }
247 };
248 }
249
250 if self.absolute {
251 match cursor.parent.get() {
255 None => true,
256 Some(p) => matches!(p.kind, NodeKind::Document),
257 }
258 } else {
259 true
260 }
261 }
262}
263
264impl Test {
265 fn matches(&self, node: &Node<'_>) -> bool {
266 match self {
267 Test::Element(name) => {
268 matches!(node.kind, NodeKind::Element) && node.name() == name.as_str()
269 }
270 Test::AnyElement => matches!(node.kind, NodeKind::Element),
271 Test::Attribute(name) => {
278 matches!(node.kind, NodeKind::Attribute) && node.name() == name.as_str()
279 }
280 Test::AnyAttribute => matches!(node.kind, NodeKind::Attribute),
281 }
282 }
283}
284
285fn predicates_hold(preds: &[Predicate], node: &Node<'_>) -> bool {
286 preds.iter().all(|p| predicate_holds(p, node))
287}
288
289fn predicate_holds(pred: &Predicate, node: &Node<'_>) -> bool {
290 match pred {
291 Predicate::Position(want) => sibling_position(node) == Some(*want),
292 Predicate::Last => {
293 let pos = sibling_position(node);
294 let cnt = sibling_count(node);
295 matches!((pos, cnt), (Some(p), Some(c)) if p == c)
296 }
297 Predicate::AttrExists(name) => node.attributes().any(|a| a.name() == name.as_str()),
298 Predicate::AttrEquals(name, val) => node
299 .attributes()
300 .any(|a| a.name() == name.as_str() && a.value() == val.as_str()),
301 }
302}
303
304fn sibling_position(node: &Node<'_>) -> Option<usize> {
306 let parent = node.parent.get()?;
307 let target_name = node.name();
308 let mut idx = 0usize;
309 for sib in parent.children() {
310 if !matches!(sib.kind, NodeKind::Element) { continue; }
311 if sib.name() != target_name { continue; }
312 idx += 1;
313 if std::ptr::eq(sib as *const _, node as *const _) {
314 return Some(idx);
315 }
316 }
317 None
318}
319
320fn sibling_count(node: &Node<'_>) -> Option<usize> {
322 let parent = node.parent.get()?;
323 let target_name = node.name();
324 let mut n = 0usize;
325 for sib in parent.children() {
326 if !matches!(sib.kind, NodeKind::Element) { continue; }
327 if sib.name() != target_name { continue; }
328 n += 1;
329 }
330 Some(n)
331}
332
333struct Parser<'a> {
336 src: &'a str,
337 pos: usize,
338}
339
340impl<'a> Parser<'a> {
341 fn new(src: &'a str) -> Self { Self { src, pos: 0 } }
342
343 fn at_eof(&self) -> bool { self.pos >= self.src.len() }
344
345 fn peek(&self) -> Option<char> { self.src[self.pos..].chars().next() }
346
347 fn bump(&mut self) -> Option<char> {
348 let c = self.peek()?;
349 self.pos += c.len_utf8();
350 Some(c)
351 }
352
353 fn skip_ws(&mut self) {
354 while let Some(c) = self.peek() {
355 if c.is_ascii_whitespace() { self.bump(); } else { break; }
356 }
357 }
358
359 fn eat(&mut self, lit: &str) -> bool {
360 if self.src[self.pos..].starts_with(lit) {
361 self.pos += lit.len();
362 true
363 } else { false }
364 }
365
366 fn parse_pattern(&mut self) -> Result<Pattern, String> {
367 let mut branches = vec![self.parse_branch()?];
368 loop {
369 self.skip_ws();
370 if !self.eat("|") { break; }
371 branches.push(self.parse_branch()?);
372 }
373 Ok(Pattern { branches })
374 }
375
376 fn parse_branch(&mut self) -> Result<Branch, String> {
377 self.skip_ws();
378 let absolute_or_descendant_root = self.peek() == Some('/');
379 let mut leading_descendant = false;
380 if absolute_or_descendant_root {
381 self.bump();
382 if self.peek() == Some('/') {
383 self.bump();
384 leading_descendant = true;
385 }
386 }
387 let mut steps_lr: Vec<Step> = vec![self.parse_step()?];
389 let mut links_lr: Vec<Link> = Vec::new();
391 loop {
392 self.skip_ws();
393 if !self.eat("/") {
394 break;
395 }
396 let link = if self.peek() == Some('/') { self.bump(); Link::Ancestor } else { Link::Parent };
397 links_lr.push(link);
398 steps_lr.push(self.parse_step()?);
399 }
400
401 steps_lr.reverse();
403 let mut links: Vec<Link> = links_lr.into_iter().rev().collect();
404
405 let absolute = if leading_descendant {
410 false
411 } else {
412 absolute_or_descendant_root
413 };
414
415 if steps_lr.len() > 1 {
419 for s in &steps_lr[1..] {
420 if matches!(s.test, Test::Attribute(_) | Test::AnyAttribute) {
421 return Err("attribute step must be the rightmost step".into());
422 }
423 }
424 }
425
426 links.truncate(steps_lr.len().saturating_sub(1));
429
430 Ok(Branch { steps: steps_lr, links, absolute })
431 }
432
433 fn parse_step(&mut self) -> Result<Step, String> {
434 self.skip_ws();
435 let test = if self.peek() == Some('@') {
436 self.bump();
437 if self.eat("*") { Test::AnyAttribute }
438 else {
439 let n = self.parse_ncname()?;
440 Test::Attribute(n)
441 }
442 } else if self.eat("*") {
443 Test::AnyElement
444 } else {
445 let n = self.parse_ncname()?;
446 Test::Element(n)
447 };
448 let mut predicates = Vec::new();
449 loop {
450 self.skip_ws();
451 if self.peek() != Some('[') { break; }
452 predicates.push(self.parse_predicate()?);
453 }
454 Ok(Step { test, predicates })
455 }
456
457 fn parse_ncname(&mut self) -> Result<String, String> {
458 let start = self.pos;
463 let first = match self.peek() {
464 Some(c) if c.is_ascii_alphabetic() || c == '_' => c,
465 _ => return Err(format!("expected NCName at offset {}", self.pos)),
466 };
467 self.bump();
468 let _ = first;
469 while let Some(c) = self.peek() {
470 if c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' || c == ':' {
471 self.bump();
472 } else { break; }
473 }
474 Ok(self.src[start..self.pos].to_string())
475 }
476
477 fn parse_predicate(&mut self) -> Result<Predicate, String> {
478 if !self.eat("[") { return Err("expected '['".into()); }
479 self.skip_ws();
480 let pred = if self.peek().is_some_and(|c| c.is_ascii_digit()) {
481 let start = self.pos;
482 while let Some(c) = self.peek() {
483 if c.is_ascii_digit() { self.bump(); } else { break; }
484 }
485 let n: usize = self.src[start..self.pos].parse().map_err(|e| format!("bad position: {e}"))?;
486 Predicate::Position(n)
487 } else if self.eat("last") {
488 self.skip_ws();
489 if !self.eat("(") || { self.skip_ws(); !self.eat(")") } {
490 return Err("expected 'last()'".into());
491 }
492 Predicate::Last
493 } else if self.peek() == Some('@') {
494 self.bump();
495 let name = self.parse_ncname()?;
496 self.skip_ws();
497 if self.eat("=") {
498 self.skip_ws();
499 let val = self.parse_string_literal()?;
500 Predicate::AttrEquals(name, val)
501 } else {
502 Predicate::AttrExists(name)
503 }
504 } else {
505 return Err(format!("unsupported predicate at offset {}", self.pos));
506 };
507 self.skip_ws();
508 if !self.eat("]") { return Err("expected ']'".into()); }
509 Ok(pred)
510 }
511
512 fn parse_string_literal(&mut self) -> Result<String, String> {
513 let q = self.bump().ok_or("expected quoted string")?;
514 if q != '"' && q != '\'' {
515 return Err(format!("expected quote, got {q:?}"));
516 }
517 let start = self.pos;
518 while let Some(c) = self.peek() {
519 if c == q { break; }
520 self.bump();
521 }
522 let s = self.src[start..self.pos].to_string();
523 if !self.eat(&q.to_string()) {
524 return Err("unterminated string literal".into());
525 }
526 Ok(s)
527 }
528}
529
530#[cfg(test)]
533mod tests {
534 use super::*;
535 use crate::{parse_str, ParseOptions};
536
537 fn parse(s: &str) -> sup_xml_tree::dom::Document {
538 parse_str(s, &ParseOptions::default()).unwrap()
539 }
540
541 fn root<'a>(d: &'a sup_xml_tree::dom::Document) -> &'a Node<'a> {
542 d.root()
543 }
544
545 fn nth_child<'a>(parent: &'a Node<'a>, i: usize) -> &'a Node<'a> {
546 parent.children().nth(i).unwrap()
547 }
548
549 #[test]
550 fn compile_basic_shapes() {
551 for src in [
552 "foo",
553 "*",
554 "@id",
555 "@*",
556 "foo/bar",
557 "foo//bar",
558 "//book",
559 "/catalog/book",
560 "foo[1]",
561 "foo[@id]",
562 "foo[@id='x']",
563 "foo[last()]",
564 "a | b | c",
565 ] {
566 Pattern::compile(src).unwrap_or_else(|e| panic!("failed on {src:?}: {e}"));
567 }
568 }
569
570 #[test]
571 fn rejects_unsupported() {
572 for src in ["foo[contains(@id,'x')]", "parent::*", "..", "foo[bar=1]"] {
573 assert!(Pattern::compile(src).is_err(), "should reject {src:?}");
574 }
575 }
576
577 #[test]
578 fn match_simple_element() {
579 let d = parse("<catalog><book/></catalog>");
580 let book = nth_child(root(&d), 0);
581 assert!(Pattern::compile("book").unwrap().matches(book));
582 assert!(!Pattern::compile("catalog").unwrap().matches(book));
583 }
584
585 #[test]
586 fn match_child_chain() {
587 let d = parse("<catalog><book/></catalog>");
588 let book = nth_child(root(&d), 0);
589 assert!(Pattern::compile("catalog/book").unwrap().matches(book));
590 assert!(!Pattern::compile("library/book").unwrap().matches(book));
591 }
592
593 #[test]
594 fn match_descendant() {
595 let d = parse("<r><a><b><c/></b></a></r>");
596 let c = nth_child(nth_child(nth_child(root(&d), 0), 0), 0);
597 assert!(Pattern::compile("//c").unwrap().matches(c));
598 assert!(Pattern::compile("r//c").unwrap().matches(c));
599 assert!(Pattern::compile("a//c").unwrap().matches(c));
600 assert!(!Pattern::compile("a/c").unwrap().matches(c));
601 }
602
603 #[test]
604 fn match_wildcard() {
605 let d = parse("<r><a/></r>");
606 let a = nth_child(root(&d), 0);
607 assert!(Pattern::compile("*").unwrap().matches(a));
608 assert!(Pattern::compile("r/*").unwrap().matches(a));
609 }
610
611 #[test]
612 fn match_absolute() {
613 let d = parse("<r><a/></r>");
614 let r = root(&d);
615 let a = nth_child(r, 0);
616 assert!(Pattern::compile("/r").unwrap().matches(r));
617 assert!(Pattern::compile("/r/a").unwrap().matches(a));
618 assert!(!Pattern::compile("/a").unwrap().matches(a));
621 }
622
623 #[test]
624 fn match_position_predicate() {
625 let d = parse("<catalog><book/><book/><book/></catalog>");
626 let books: Vec<_> = root(&d).children().collect();
627 assert!(Pattern::compile("book[1]").unwrap().matches(books[0]));
628 assert!(!Pattern::compile("book[1]").unwrap().matches(books[1]));
629 assert!(Pattern::compile("book[2]").unwrap().matches(books[1]));
630 assert!(Pattern::compile("book[last()]").unwrap().matches(books[2]));
631 assert!(!Pattern::compile("book[last()]").unwrap().matches(books[0]));
632 }
633
634 #[test]
635 fn match_attr_predicate() {
636 let d = parse(r#"<catalog><book id="b1"/><book/></catalog>"#);
637 let books: Vec<_> = root(&d).children().collect();
638 assert!(Pattern::compile("book[@id]").unwrap().matches(books[0]));
639 assert!(!Pattern::compile("book[@id]").unwrap().matches(books[1]));
640 assert!(Pattern::compile(r#"book[@id="b1"]"#).unwrap().matches(books[0]));
641 assert!(!Pattern::compile(r#"book[@id="b2"]"#).unwrap().matches(books[0]));
642 }
643
644 #[test]
645 fn match_union() {
646 let d = parse("<r><a/><b/></r>");
647 let a = nth_child(root(&d), 0);
648 let b = nth_child(root(&d), 1);
649 let p = Pattern::compile("a | b").unwrap();
650 assert!(p.matches(a));
651 assert!(p.matches(b));
652 let p2 = Pattern::compile("a | c").unwrap();
653 assert!(p2.matches(a));
654 assert!(!p2.matches(b));
655 }
656}