1use crate::{AutomataError, Element};
75
76#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct SelectorPath {
80 steps: Vec<PathStep>,
81}
82
83impl<'de> serde::Deserialize<'de> for SelectorPath {
84 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
85 let s = String::deserialize(d)?;
86 SelectorPath::parse(&s).map_err(serde::de::Error::custom)
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
91struct PathStep {
92 combinator: Combinator,
93 predicates: Vec<Predicate>,
94 nth: Option<usize>, ascend: usize, }
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99enum Combinator {
100 Root,
102 Child,
104 Descendant,
106}
107
108#[derive(Debug, Clone, PartialEq, Eq)]
109struct Predicate {
110 attr: Attr,
111 op: Op,
112 values: Vec<String>,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118enum Attr {
119 Role,
120 Name,
121 Title, AutomationId,
123 Url, }
125
126#[derive(Debug, Clone, PartialEq, Eq)]
127enum Op {
128 Exact, Contains, StartsWith, EndsWith, }
133
134impl SelectorPath {
137 pub fn is_wildcard(&self) -> bool {
139 matches!(self.steps.as_slice(), [step] if step.predicates.is_empty())
140 }
141
142 pub fn parse(input: &str) -> Result<Self, AutomataError> {
143 let input = input.trim();
144 if input.is_empty() {
145 return Err(AutomataError::Internal("empty selector".into()));
146 }
147
148 let segments = split_segments(input)?;
152 if segments.is_empty() {
153 return Err(AutomataError::Internal("empty selector".into()));
154 }
155
156 let mut steps = Vec::with_capacity(segments.len());
157 for (combinator, seg) in segments {
158 steps.push(parse_step(combinator, seg)?);
159 }
160
161 Ok(SelectorPath { steps })
162 }
163
164 pub fn find_one<E: Element>(&self, root: &E) -> Option<E> {
171 if self.steps.is_empty() {
172 return None;
173 }
174 match_steps(root, &self.steps, Some(1)).into_iter().next()
175 }
176
177 pub fn find_all<E: Element>(&self, root: &E) -> Vec<E> {
179 if self.steps.is_empty() {
180 return vec![];
181 }
182 match_steps(root, &self.steps, None)
183 }
184
185 pub fn matches<E: Element>(&self, element: &E) -> bool {
188 match self.steps.first() {
189 Some(step) => step_matches(step, element),
190 None => false,
191 }
192 }
193
194 pub fn find_one_with_parent<E: Element>(&self, root: &E) -> Option<(E, Option<E>)> {
203 if self.steps.is_empty() {
204 return None;
205 }
206 find_first_with_step_parent(root, &self.steps)
207 }
208
209 pub fn matches_tab_info(&self, title: &str, url: &str) -> bool {
215 let Some(step) = self.steps.first() else {
216 return false;
217 };
218 step.predicates.iter().all(|p| {
219 let actual = match p.attr {
220 Attr::Name | Attr::Title => title,
221 Attr::Url => url,
222 Attr::Role | Attr::AutomationId => return true,
224 };
225 p.values.iter().any(|v| match p.op {
226 Op::Exact => actual == v.as_str(),
227 Op::Contains => actual
228 .to_ascii_lowercase()
229 .contains(v.to_ascii_lowercase().as_str()),
230 Op::StartsWith => actual
231 .to_ascii_lowercase()
232 .starts_with(v.to_ascii_lowercase().as_str()),
233 Op::EndsWith => actual
234 .to_ascii_lowercase()
235 .ends_with(v.to_ascii_lowercase().as_str()),
236 })
237 })
238 }
239
240 pub fn find_one_from_step_parent<E: Element>(&self, step_parent: &E) -> Option<E> {
249 let step = self.steps.last()?;
250 match &step.combinator {
251 Combinator::Root | Combinator::Child => {
252 let children = step_parent.children().ok()?;
253 apply_nth(
254 children
255 .into_iter()
256 .filter(|c| step_matches(step, c))
257 .collect(),
258 step.nth,
259 )
260 .into_iter()
261 .next()
262 .and_then(|el| {
263 if step.ascend > 0 {
264 ascend_n(el, step.ascend)
265 } else {
266 Some(el)
267 }
268 })
269 }
270 Combinator::Descendant => {
271 let mut acc = vec![];
272 let limit = if step.nth.is_none() { Some(1) } else { None };
273 collect_descendants(step_parent, step, &mut acc, limit);
274 apply_nth(acc, step.nth).into_iter().next().and_then(|el| {
275 if step.ascend > 0 {
276 ascend_n(el, step.ascend)
277 } else {
278 Some(el)
279 }
280 })
281 }
282 }
283 }
284}
285
286fn ascend_n<E: Element>(el: E, n: usize) -> Option<E> {
291 let mut cur = el;
292 for _ in 0..n {
293 cur = cur.parent()?;
294 }
295 Some(cur)
296}
297
298fn match_steps<E: Element>(origin: &E, steps: &[PathStep], limit: Option<usize>) -> Vec<E> {
305 let step = &steps[0];
306 let rest = &steps[1..];
307
308 let candidates: Vec<E> = match &step.combinator {
309 Combinator::Root => {
310 if step_matches(step, origin) {
312 vec![origin.clone()]
313 } else {
314 vec![]
315 }
316 }
317 Combinator::Child => {
318 match origin.children() {
320 Ok(children) => children
321 .into_iter()
322 .filter(|c| step_matches(step, c))
323 .collect(),
324 Err(e) => {
325 log::debug!(
326 "selector: children() failed on '{}' ({}): {e}",
327 origin.name().unwrap_or_default(),
328 origin.role()
329 );
330 vec![]
331 }
332 }
333 }
334 Combinator::Descendant => {
335 let mut acc = vec![];
337 let step_limit = if rest.is_empty() && step.nth.is_none() {
342 limit
343 } else {
344 None
345 };
346 collect_descendants(origin, step, &mut acc, step_limit);
347 acc
348 }
349 };
350
351 let candidates = apply_nth(candidates, step.nth);
353
354 let candidates: Vec<E> = if step.ascend > 0 {
356 candidates
357 .into_iter()
358 .filter_map(|c| ascend_n(c, step.ascend))
359 .collect()
360 } else {
361 candidates
362 };
363
364 if rest.is_empty() {
365 candidates
366 } else {
367 let mut results = Vec::new();
369 for c in candidates {
370 let remaining = limit.map(|l| l.saturating_sub(results.len()));
371 results.extend(match_steps(&c, rest, remaining));
372 if limit.is_some_and(|l| results.len() >= l) {
373 break;
374 }
375 }
376 results
377 }
378}
379
380fn collect_descendants<E: Element>(
381 parent: &E,
382 step: &PathStep,
383 acc: &mut Vec<E>,
384 limit: Option<usize>,
385) {
386 if limit.is_some_and(|l| acc.len() >= l) {
387 return;
388 }
389 let children = match parent.children() {
390 Ok(c) => c,
391 Err(e) => {
392 log::debug!(
393 "selector: children() failed on '{}' ({}): {e}",
394 parent.name().unwrap_or_default(),
395 parent.role()
396 );
397 return;
398 }
399 };
400 for child in children {
401 if step_matches(step, &child) {
402 acc.push(child.clone());
403 if limit.is_some_and(|l| acc.len() >= l) {
404 return;
405 }
406 }
407 collect_descendants(&child, step, acc, limit);
408 if limit.is_some_and(|l| acc.len() >= l) {
409 return;
410 }
411 }
412}
413
414fn collect_first_with_parent<E: Element>(parent: &E, step: &PathStep) -> Option<(E, E)> {
419 let children = match parent.children() {
420 Ok(c) => c,
421 Err(_) => return None,
422 };
423 for child in children {
424 if step_matches(step, &child) {
425 return Some((child, parent.clone()));
426 }
427 if let Some(found) = collect_first_with_parent(&child, step) {
428 return Some(found);
429 }
430 }
431 None
432}
433
434fn find_first_with_step_parent<E: Element>(
436 origin: &E,
437 steps: &[PathStep],
438) -> Option<(E, Option<E>)> {
439 let step = &steps[0];
440 let rest = &steps[1..];
441
442 if rest.is_empty() {
443 return match &step.combinator {
445 Combinator::Root => {
446 if step_matches(step, origin) {
447 let el = origin.clone();
448 if step.ascend > 0 {
449 ascend_n(el, step.ascend).map(|a| (a, None))
450 } else {
451 Some((el, None))
452 }
453 } else {
454 None
455 }
456 }
457 Combinator::Child => {
458 let children = origin.children().ok()?;
459 let matched = apply_nth(
460 children
461 .into_iter()
462 .filter(|c| step_matches(step, c))
463 .collect(),
464 step.nth,
465 )
466 .into_iter()
467 .next();
468 match matched {
469 None => None,
470 Some(el) if step.ascend > 0 => ascend_n(el, step.ascend).map(|a| (a, None)),
471 Some(el) => Some((el, Some(origin.clone()))),
472 }
473 }
474 Combinator::Descendant => {
475 if step.nth.is_some() {
476 let mut acc = vec![];
480 collect_descendants(origin, step, &mut acc, None);
481 apply_nth(acc, step.nth).into_iter().next().and_then(|el| {
482 if step.ascend > 0 {
483 ascend_n(el, step.ascend).map(|a| (a, None))
484 } else {
485 Some((el, Some(origin.clone())))
486 }
487 })
488 } else if step.ascend > 0 {
489 collect_first_with_parent(origin, step)
490 .and_then(|(el, _)| ascend_n(el, step.ascend).map(|a| (a, None)))
491 } else {
492 collect_first_with_parent(origin, step).map(|(el, parent)| (el, Some(parent)))
493 }
494 }
495 };
496 }
497
498 let candidates: Vec<E> = match &step.combinator {
501 Combinator::Root => {
502 if step_matches(step, origin) {
503 vec![origin.clone()]
504 } else {
505 vec![]
506 }
507 }
508 Combinator::Child => match origin.children() {
509 Ok(c) => c.into_iter().filter(|c| step_matches(step, c)).collect(),
510 Err(_) => vec![],
511 },
512 Combinator::Descendant => {
513 let mut acc = vec![];
514 collect_descendants(origin, step, &mut acc, None);
515 acc
516 }
517 };
518 let candidates = apply_nth(candidates, step.nth);
519 let candidates: Vec<E> = if step.ascend > 0 {
520 candidates
521 .into_iter()
522 .filter_map(|c| ascend_n(c, step.ascend))
523 .collect()
524 } else {
525 candidates
526 };
527
528 for candidate in candidates {
529 if let Some(result) = find_first_with_step_parent(&candidate, rest) {
530 return Some(result);
531 }
532 }
533 None
534}
535
536fn apply_nth<E>(candidates: Vec<E>, nth: Option<usize>) -> Vec<E> {
537 match nth {
538 None => candidates,
539 Some(n) => candidates.into_iter().nth(n).into_iter().collect(),
540 }
541}
542
543fn step_matches<E: Element>(step: &PathStep, element: &E) -> bool {
544 step.predicates
545 .iter()
546 .all(|p| predicate_matches(p, element))
547}
548
549fn predicate_matches<E: Element>(pred: &Predicate, element: &E) -> bool {
550 let actual = match pred.attr {
551 Attr::Role => element.role(),
552 Attr::Name | Attr::Title => element.name().unwrap_or_default(),
553 Attr::AutomationId => element.automation_id().unwrap_or_default(),
554 Attr::Url => String::new(),
556 };
557 pred.values.iter().any(|v| match pred.op {
558 Op::Exact => actual == v.as_str(),
559 Op::Contains => actual
560 .to_ascii_lowercase()
561 .contains(v.to_ascii_lowercase().as_str()),
562 Op::StartsWith => actual
563 .to_ascii_lowercase()
564 .starts_with(v.to_ascii_lowercase().as_str()),
565 Op::EndsWith => actual
566 .to_ascii_lowercase()
567 .ends_with(v.to_ascii_lowercase().as_str()),
568 })
569}
570
571fn split_segments(input: &str) -> Result<Vec<(Combinator, &str)>, AutomataError> {
577 let bytes = input.as_bytes();
578 let mut segments: Vec<(Combinator, &str)> = vec![];
579 let mut depth = 0usize;
580 let mut seg_start = 0;
581 let mut i = 0;
582 let mut pending: Option<Combinator> = None;
584
585 while i < bytes.len() {
586 match bytes[i] {
587 b'[' => depth += 1,
588 b']' => depth = depth.saturating_sub(1),
589 b'>' if depth == 0 => {
590 let seg = input[seg_start..i].trim();
591 if !seg.is_empty() {
592 let combinator = pending.take().unwrap_or(if segments.is_empty() {
593 Combinator::Root
594 } else {
595 Combinator::Child
596 });
597 segments.push((combinator, seg));
598 }
599 let is_descendant = bytes.get(i + 1) == Some(&b'>');
601 pending = Some(if is_descendant {
602 i += 1; Combinator::Descendant
604 } else {
605 Combinator::Child
606 });
607 seg_start = i + 1;
608 }
609 _ => {}
610 }
611 i += 1;
612 }
613
614 let tail = input[seg_start..].trim();
616 if !tail.is_empty() {
617 let combinator = pending.take().unwrap_or(if segments.is_empty() {
618 Combinator::Root
619 } else {
620 Combinator::Child
621 });
622 segments.push((combinator, tail));
623 }
624
625 if depth != 0 {
626 return Err(AutomataError::Internal("unclosed '[' in selector".into()));
627 }
628
629 if segments.is_empty() {
630 return Err(AutomataError::Internal(
631 "selector produced no segments".into(),
632 ));
633 }
634
635 Ok(segments)
636}
637
638fn parse_step(combinator: Combinator, seg: &str) -> Result<PathStep, AutomataError> {
639 let seg = seg.trim();
640
641 let (seg, nth, ascend) = extract_pseudos(seg)?;
643
644 if seg == "*" {
646 return Ok(PathStep {
647 combinator,
648 predicates: vec![],
649 nth,
650 ascend,
651 });
652 }
653
654 let (bare_role, rest) = split_bare_role(seg);
657
658 let mut predicates: Vec<Predicate> = vec![];
659
660 if !bare_role.is_empty() {
661 predicates.push(Predicate {
662 attr: Attr::Role,
663 op: Op::Exact,
664 values: vec![bare_role.to_string()],
665 });
666 }
667
668 let mut s = rest.trim();
670 while s.starts_with('[') {
671 let close = s.find(']').ok_or_else(|| {
672 AutomataError::Internal(format!("unclosed '[' in selector segment: {seg}"))
673 })?;
674 let inner = &s[1..close];
675 predicates.push(parse_predicate(inner)?);
676 s = s[close + 1..].trim();
677 }
678
679 if predicates.is_empty() {
680 return Err(AutomataError::Internal(format!(
681 "selector step has no predicates: '{seg}'"
682 )));
683 }
684
685 Ok(PathStep {
686 combinator,
687 predicates,
688 nth,
689 ascend,
690 })
691}
692
693fn extract_pseudos(seg: &str) -> Result<(&str, Option<usize>, usize), AutomataError> {
697 let mut s = seg;
698 let mut nth: Option<usize> = None;
699 let mut ascend: usize = 0;
700
701 loop {
702 let t = s.trim_end();
703 if t.ends_with(":parent") {
704 s = &t[..t.len() - ":parent".len()];
705 ascend = 1;
706 continue;
707 }
708 if t.ends_with(')') {
709 if let Some(open) = t.rfind('(') {
710 let before = &t[..open];
711 let inner = &t[open + 1..t.len() - 1];
712 if before.ends_with(":ancestor") {
713 let n = inner.trim().parse::<usize>().map_err(|_| {
714 AutomataError::Internal(format!("invalid :ancestor index in '{seg}'"))
715 })?;
716 s = &before[..before.len() - ":ancestor".len()];
717 ascend = n;
718 continue;
719 }
720 if before.ends_with(":nth") {
721 let n = inner.trim().parse::<usize>().map_err(|_| {
722 AutomataError::Internal(format!("invalid :nth index in '{seg}'"))
723 })?;
724 s = &before[..before.len() - ":nth".len()];
725 nth = Some(n);
726 continue;
727 }
728 }
729 }
730 break;
731 }
732
733 Ok((s, nth, ascend))
734}
735
736fn split_bare_role(seg: &str) -> (&str, &str) {
739 if let Some(pos) = seg.find('[') {
740 (&seg[..pos], &seg[pos..])
741 } else {
742 (seg, "")
743 }
744}
745
746fn parse_predicate(inner: &str) -> Result<Predicate, AutomataError> {
748 let inner = inner.trim();
753
754 let (attr_str, op, value) = if let Some(pos) = inner.find("~=") {
755 (&inner[..pos], Op::Contains, inner[pos + 2..].trim())
756 } else if let Some(pos) = inner.find("^=") {
757 (&inner[..pos], Op::StartsWith, inner[pos + 2..].trim())
758 } else if let Some(pos) = inner.find("$=") {
759 (&inner[..pos], Op::EndsWith, inner[pos + 2..].trim())
760 } else if let Some(pos) = inner.find('=') {
761 (&inner[..pos], Op::Exact, inner[pos + 1..].trim())
762 } else {
763 return Err(AutomataError::Internal(format!(
764 "no operator found in predicate: '{inner}'"
765 )));
766 };
767
768 let attr = match attr_str.trim() {
769 "role" => Attr::Role,
770 "name" => Attr::Name,
771 "title" => Attr::Title,
772 "id" | "automation_id" => Attr::AutomationId,
773 "url" => Attr::Url,
774 other => {
775 return Err(AutomataError::Internal(format!(
776 "unknown attribute '{other}' in selector"
777 )));
778 }
779 };
780
781 let values: Vec<String> = value
784 .split('|')
785 .map(|v| v.trim().trim_matches(|c| c == '\'' || c == '"').to_string())
786 .collect();
787
788 Ok(Predicate { attr, op, values })
789}
790
791impl std::fmt::Display for SelectorPath {
794 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
795 for (i, step) in self.steps.iter().enumerate() {
796 if i > 0 {
797 match step.combinator {
798 Combinator::Child => write!(f, " > ")?,
799 Combinator::Descendant => write!(f, " >> ")?,
800 Combinator::Root => {}
801 }
802 }
803 if step.predicates.is_empty() {
804 write!(f, "*")?;
805 }
806 for pred in &step.predicates {
807 let op = match pred.op {
808 Op::Exact => "=",
809 Op::Contains => "~=",
810 Op::StartsWith => "^=",
811 Op::EndsWith => "$=",
812 };
813 let attr = match pred.attr {
814 Attr::Role => "role",
815 Attr::Name | Attr::Title => "name",
816 Attr::AutomationId => "id",
817 Attr::Url => "url",
818 };
819 write!(f, "[{attr}{op}{}]", pred.values.join("|"))?;
820 }
821 if let Some(n) = step.nth {
822 write!(f, ":nth({n})")?;
823 }
824 match step.ascend {
825 0 => {}
826 1 => write!(f, ":parent")?,
827 n => write!(f, ":ancestor({n})")?,
828 }
829 }
830 Ok(())
831 }
832}