rama_http/protocols/html/selector/
matcher.rs1use super::ast::{
8 AttributeSelector, Combinator, ComplexSelector, Compound, NthType, Selector, SelectorPart,
9};
10
11pub trait SelectorSubject: Sized {
23 fn local_name(&self) -> &str;
25
26 fn attribute(&self, name: &str) -> Option<&str>;
28
29 fn parent(&self) -> Option<Self>;
31
32 fn nth_child_index(&self) -> usize;
35
36 fn nth_of_type_index(&self) -> usize;
39
40 fn has_id(&self, id: &str) -> bool {
44 self.attribute("id") == Some(id)
45 }
46
47 fn has_class(&self, class: &str) -> bool {
51 self.attribute("class")
52 .is_some_and(|value| value.split_ascii_whitespace().any(|c| c == class))
53 }
54}
55
56impl Selector {
57 pub fn matches<S: SelectorSubject>(&self, subject: &S) -> bool {
59 self.selectors.iter().any(|c| complex_matches(c, subject))
60 }
61}
62
63fn complex_matches<S: SelectorSubject>(complex: &ComplexSelector, subject: &S) -> bool {
64 match_from(&complex.parts, complex.parts.len() - 1, subject)
65}
66
67fn match_from<S: SelectorSubject>(parts: &[SelectorPart], idx: usize, subject: &S) -> bool {
70 let part = &parts[idx];
71 if !compound_matches(&part.compound, subject) {
72 return false;
73 }
74 if idx == 0 {
75 return true;
76 }
77
78 let Some(combinator) = part.combinator else {
79 return false;
80 };
81 match combinator {
82 Combinator::Child => subject
83 .parent()
84 .is_some_and(|parent| match_from(parts, idx - 1, &parent)),
85 Combinator::Descendant => {
86 let mut ancestor = subject.parent();
87 while let Some(node) = ancestor {
88 if match_from(parts, idx - 1, &node) {
89 return true;
90 }
91 ancestor = node.parent();
92 }
93 false
94 }
95 }
96}
97
98fn compound_matches<S: SelectorSubject>(compound: &Compound, subject: &S) -> bool {
99 if let Some(name) = &compound.name
100 && !name.matches(subject.local_name())
101 {
102 return false;
103 }
104 if let Some(id) = &compound.id
105 && !subject.has_id(id)
106 {
107 return false;
108 }
109 if !compound.classes.iter().all(|c| subject.has_class(c)) {
110 return false;
111 }
112 if !compound
113 .attributes
114 .iter()
115 .all(|a| attribute_matches(a, subject))
116 {
117 return false;
118 }
119 for nth in &compound.nth {
120 let index = match nth.ty {
121 NthType::Child => subject.nth_child_index(),
122 NthType::OfType => subject.nth_of_type_index(),
123 };
124 if !nth.matches_index(index) {
125 return false;
126 }
127 }
128 !compound
130 .negations
131 .iter()
132 .any(|neg| compound_matches(neg, subject))
133}
134
135fn attribute_matches<S: SelectorSubject>(selector: &AttributeSelector, subject: &S) -> bool {
136 subject
137 .attribute(&selector.name)
138 .is_some_and(|actual| selector.matches_value(actual.as_bytes()))
139}