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
128impl Branch {
131 fn matches(&self, node: &Node<'_>) -> bool {
132 let Some(mut cursor) = Some(node) else { return false; };
135
136 for (i, step) in self.steps.iter().enumerate() {
137 if !step.test.matches(cursor) {
139 return false;
140 }
141 if !predicates_hold(&step.predicates, cursor) {
142 return false;
143 }
144
145 let Some(link) = self.links.get(i) else { break; };
147
148 cursor = match link {
149 Link::Parent => match cursor.parent.get() {
150 Some(p) => p,
151 None => return false,
152 },
153 Link::Ancestor => {
154 let Some(next_step) = self.steps.get(i + 1) else {
158 return false;
159 };
160 let mut found = None;
161 let mut up = cursor.parent.get();
162 while let Some(anc) = up {
163 if next_step.test.matches(anc) && predicates_hold(&next_step.predicates, anc) {
164 found = Some(anc);
165 break;
166 }
167 up = anc.parent.get();
168 }
169 match found {
170 Some(a) => {
171 a
180 }
181 None => return false,
182 }
183 }
184 };
185 }
186
187 if self.absolute {
188 match cursor.parent.get() {
192 None => true,
193 Some(p) => matches!(p.kind, NodeKind::Document),
194 }
195 } else {
196 true
197 }
198 }
199}
200
201impl Test {
202 fn matches(&self, node: &Node<'_>) -> bool {
203 match self {
204 Test::Element(name) => {
205 matches!(node.kind, NodeKind::Element) && node.name() == name.as_str()
206 }
207 Test::AnyElement => matches!(node.kind, NodeKind::Element),
208 Test::Attribute(name) => {
215 matches!(node.kind, NodeKind::Attribute) && node.name() == name.as_str()
216 }
217 Test::AnyAttribute => matches!(node.kind, NodeKind::Attribute),
218 }
219 }
220}
221
222fn predicates_hold(preds: &[Predicate], node: &Node<'_>) -> bool {
223 preds.iter().all(|p| predicate_holds(p, node))
224}
225
226fn predicate_holds(pred: &Predicate, node: &Node<'_>) -> bool {
227 match pred {
228 Predicate::Position(want) => sibling_position(node) == Some(*want),
229 Predicate::Last => {
230 let pos = sibling_position(node);
231 let cnt = sibling_count(node);
232 matches!((pos, cnt), (Some(p), Some(c)) if p == c)
233 }
234 Predicate::AttrExists(name) => node.attributes().any(|a| a.name() == name.as_str()),
235 Predicate::AttrEquals(name, val) => node
236 .attributes()
237 .any(|a| a.name() == name.as_str() && a.value() == val.as_str()),
238 }
239}
240
241fn sibling_position(node: &Node<'_>) -> Option<usize> {
243 let parent = node.parent.get()?;
244 let target_name = node.name();
245 let mut idx = 0usize;
246 for sib in parent.children() {
247 if !matches!(sib.kind, NodeKind::Element) { continue; }
248 if sib.name() != target_name { continue; }
249 idx += 1;
250 if std::ptr::eq(sib as *const _, node as *const _) {
251 return Some(idx);
252 }
253 }
254 None
255}
256
257fn sibling_count(node: &Node<'_>) -> Option<usize> {
259 let parent = node.parent.get()?;
260 let target_name = node.name();
261 let mut n = 0usize;
262 for sib in parent.children() {
263 if !matches!(sib.kind, NodeKind::Element) { continue; }
264 if sib.name() != target_name { continue; }
265 n += 1;
266 }
267 Some(n)
268}
269
270struct Parser<'a> {
273 src: &'a str,
274 pos: usize,
275}
276
277impl<'a> Parser<'a> {
278 fn new(src: &'a str) -> Self { Self { src, pos: 0 } }
279
280 fn at_eof(&self) -> bool { self.pos >= self.src.len() }
281
282 fn peek(&self) -> Option<char> { self.src[self.pos..].chars().next() }
283
284 fn bump(&mut self) -> Option<char> {
285 let c = self.peek()?;
286 self.pos += c.len_utf8();
287 Some(c)
288 }
289
290 fn skip_ws(&mut self) {
291 while let Some(c) = self.peek() {
292 if c.is_ascii_whitespace() { self.bump(); } else { break; }
293 }
294 }
295
296 fn eat(&mut self, lit: &str) -> bool {
297 if self.src[self.pos..].starts_with(lit) {
298 self.pos += lit.len();
299 true
300 } else { false }
301 }
302
303 fn parse_pattern(&mut self) -> Result<Pattern, String> {
304 let mut branches = vec![self.parse_branch()?];
305 loop {
306 self.skip_ws();
307 if !self.eat("|") { break; }
308 branches.push(self.parse_branch()?);
309 }
310 Ok(Pattern { branches })
311 }
312
313 fn parse_branch(&mut self) -> Result<Branch, String> {
314 self.skip_ws();
315 let absolute_or_descendant_root = self.peek() == Some('/');
316 let mut leading_descendant = false;
317 if absolute_or_descendant_root {
318 self.bump();
319 if self.peek() == Some('/') {
320 self.bump();
321 leading_descendant = true;
322 }
323 }
324 let mut steps_lr: Vec<Step> = vec![self.parse_step()?];
326 let mut links_lr: Vec<Link> = Vec::new();
328 loop {
329 self.skip_ws();
330 if !self.eat("/") {
331 break;
332 }
333 let link = if self.peek() == Some('/') { self.bump(); Link::Ancestor } else { Link::Parent };
334 links_lr.push(link);
335 steps_lr.push(self.parse_step()?);
336 }
337
338 steps_lr.reverse();
340 let mut links: Vec<Link> = links_lr.into_iter().rev().collect();
341
342 let absolute = if leading_descendant {
347 false
348 } else {
349 absolute_or_descendant_root
350 };
351
352 if steps_lr.len() > 1 {
356 for s in &steps_lr[1..] {
357 if matches!(s.test, Test::Attribute(_) | Test::AnyAttribute) {
358 return Err("attribute step must be the rightmost step".into());
359 }
360 }
361 }
362
363 links.truncate(steps_lr.len().saturating_sub(1));
366
367 Ok(Branch { steps: steps_lr, links, absolute })
368 }
369
370 fn parse_step(&mut self) -> Result<Step, String> {
371 self.skip_ws();
372 let test = if self.peek() == Some('@') {
373 self.bump();
374 if self.eat("*") { Test::AnyAttribute }
375 else {
376 let n = self.parse_ncname()?;
377 Test::Attribute(n)
378 }
379 } else if self.eat("*") {
380 Test::AnyElement
381 } else {
382 let n = self.parse_ncname()?;
383 Test::Element(n)
384 };
385 let mut predicates = Vec::new();
386 loop {
387 self.skip_ws();
388 if self.peek() != Some('[') { break; }
389 predicates.push(self.parse_predicate()?);
390 }
391 Ok(Step { test, predicates })
392 }
393
394 fn parse_ncname(&mut self) -> Result<String, String> {
395 let start = self.pos;
400 let first = match self.peek() {
401 Some(c) if c.is_ascii_alphabetic() || c == '_' => c,
402 _ => return Err(format!("expected NCName at offset {}", self.pos)),
403 };
404 self.bump();
405 let _ = first;
406 while let Some(c) = self.peek() {
407 if c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' || c == ':' {
408 self.bump();
409 } else { break; }
410 }
411 Ok(self.src[start..self.pos].to_string())
412 }
413
414 fn parse_predicate(&mut self) -> Result<Predicate, String> {
415 if !self.eat("[") { return Err("expected '['".into()); }
416 self.skip_ws();
417 let pred = if self.peek().is_some_and(|c| c.is_ascii_digit()) {
418 let start = self.pos;
419 while let Some(c) = self.peek() {
420 if c.is_ascii_digit() { self.bump(); } else { break; }
421 }
422 let n: usize = self.src[start..self.pos].parse().map_err(|e| format!("bad position: {e}"))?;
423 Predicate::Position(n)
424 } else if self.eat("last") {
425 self.skip_ws();
426 if !self.eat("(") || { self.skip_ws(); !self.eat(")") } {
427 return Err("expected 'last()'".into());
428 }
429 Predicate::Last
430 } else if self.peek() == Some('@') {
431 self.bump();
432 let name = self.parse_ncname()?;
433 self.skip_ws();
434 if self.eat("=") {
435 self.skip_ws();
436 let val = self.parse_string_literal()?;
437 Predicate::AttrEquals(name, val)
438 } else {
439 Predicate::AttrExists(name)
440 }
441 } else {
442 return Err(format!("unsupported predicate at offset {}", self.pos));
443 };
444 self.skip_ws();
445 if !self.eat("]") { return Err("expected ']'".into()); }
446 Ok(pred)
447 }
448
449 fn parse_string_literal(&mut self) -> Result<String, String> {
450 let q = self.bump().ok_or("expected quoted string")?;
451 if q != '"' && q != '\'' {
452 return Err(format!("expected quote, got {q:?}"));
453 }
454 let start = self.pos;
455 while let Some(c) = self.peek() {
456 if c == q { break; }
457 self.bump();
458 }
459 let s = self.src[start..self.pos].to_string();
460 if !self.eat(&q.to_string()) {
461 return Err("unterminated string literal".into());
462 }
463 Ok(s)
464 }
465}
466
467#[cfg(test)]
470mod tests {
471 use super::*;
472 use crate::{parse_str, ParseOptions};
473
474 fn parse(s: &str) -> sup_xml_tree::dom::Document {
475 parse_str(s, &ParseOptions::default()).unwrap()
476 }
477
478 fn root<'a>(d: &'a sup_xml_tree::dom::Document) -> &'a Node<'a> {
479 d.root()
480 }
481
482 fn nth_child<'a>(parent: &'a Node<'a>, i: usize) -> &'a Node<'a> {
483 parent.children().nth(i).unwrap()
484 }
485
486 #[test]
487 fn compile_basic_shapes() {
488 for src in [
489 "foo",
490 "*",
491 "@id",
492 "@*",
493 "foo/bar",
494 "foo//bar",
495 "//book",
496 "/catalog/book",
497 "foo[1]",
498 "foo[@id]",
499 "foo[@id='x']",
500 "foo[last()]",
501 "a | b | c",
502 ] {
503 Pattern::compile(src).unwrap_or_else(|e| panic!("failed on {src:?}: {e}"));
504 }
505 }
506
507 #[test]
508 fn rejects_unsupported() {
509 for src in ["foo[contains(@id,'x')]", "parent::*", "..", "foo[bar=1]"] {
510 assert!(Pattern::compile(src).is_err(), "should reject {src:?}");
511 }
512 }
513
514 #[test]
515 fn match_simple_element() {
516 let d = parse("<catalog><book/></catalog>");
517 let book = nth_child(root(&d), 0);
518 assert!(Pattern::compile("book").unwrap().matches(book));
519 assert!(!Pattern::compile("catalog").unwrap().matches(book));
520 }
521
522 #[test]
523 fn match_child_chain() {
524 let d = parse("<catalog><book/></catalog>");
525 let book = nth_child(root(&d), 0);
526 assert!(Pattern::compile("catalog/book").unwrap().matches(book));
527 assert!(!Pattern::compile("library/book").unwrap().matches(book));
528 }
529
530 #[test]
531 fn match_descendant() {
532 let d = parse("<r><a><b><c/></b></a></r>");
533 let c = nth_child(nth_child(nth_child(root(&d), 0), 0), 0);
534 assert!(Pattern::compile("//c").unwrap().matches(c));
535 assert!(Pattern::compile("r//c").unwrap().matches(c));
536 assert!(Pattern::compile("a//c").unwrap().matches(c));
537 assert!(!Pattern::compile("a/c").unwrap().matches(c));
538 }
539
540 #[test]
541 fn match_wildcard() {
542 let d = parse("<r><a/></r>");
543 let a = nth_child(root(&d), 0);
544 assert!(Pattern::compile("*").unwrap().matches(a));
545 assert!(Pattern::compile("r/*").unwrap().matches(a));
546 }
547
548 #[test]
549 fn match_absolute() {
550 let d = parse("<r><a/></r>");
551 let r = root(&d);
552 let a = nth_child(r, 0);
553 assert!(Pattern::compile("/r").unwrap().matches(r));
554 assert!(Pattern::compile("/r/a").unwrap().matches(a));
555 assert!(!Pattern::compile("/a").unwrap().matches(a));
558 }
559
560 #[test]
561 fn match_position_predicate() {
562 let d = parse("<catalog><book/><book/><book/></catalog>");
563 let books: Vec<_> = root(&d).children().collect();
564 assert!(Pattern::compile("book[1]").unwrap().matches(books[0]));
565 assert!(!Pattern::compile("book[1]").unwrap().matches(books[1]));
566 assert!(Pattern::compile("book[2]").unwrap().matches(books[1]));
567 assert!(Pattern::compile("book[last()]").unwrap().matches(books[2]));
568 assert!(!Pattern::compile("book[last()]").unwrap().matches(books[0]));
569 }
570
571 #[test]
572 fn match_attr_predicate() {
573 let d = parse(r#"<catalog><book id="b1"/><book/></catalog>"#);
574 let books: Vec<_> = root(&d).children().collect();
575 assert!(Pattern::compile("book[@id]").unwrap().matches(books[0]));
576 assert!(!Pattern::compile("book[@id]").unwrap().matches(books[1]));
577 assert!(Pattern::compile(r#"book[@id="b1"]"#).unwrap().matches(books[0]));
578 assert!(!Pattern::compile(r#"book[@id="b2"]"#).unwrap().matches(books[0]));
579 }
580
581 #[test]
582 fn match_union() {
583 let d = parse("<r><a/><b/></r>");
584 let a = nth_child(root(&d), 0);
585 let b = nth_child(root(&d), 1);
586 let p = Pattern::compile("a | b").unwrap();
587 assert!(p.matches(a));
588 assert!(p.matches(b));
589 let p2 = Pattern::compile("a | c").unwrap();
590 assert!(p2.matches(a));
591 assert!(!p2.matches(b));
592 }
593}