1use alloc::string::{String, ToString};
16use alloc::vec::Vec;
17use core::iter::Peekable;
18use core::str::Chars;
19
20use crate::tree::{Dom, NodeRef};
21
22impl Dom {
23 pub fn select<'a>(&'a self, selector: &str) -> impl Iterator<Item = NodeRef<'a>> + 'a {
37 let groups = parse_selector(selector);
38 self.nodes()
39 .filter(move |n| n.is_element() && groups.iter().any(|g| matches_complex(*n, g)))
40 }
41}
42
43#[derive(Clone, Copy)]
44enum Comb {
45 Descendant,
46 Child,
47}
48
49struct Compound {
50 tag: Option<String>,
51 id: Option<String>,
52 classes: Vec<String>,
53 attrs: Vec<AttrPred>,
54}
55
56struct AttrPred {
57 name: String,
58 value: Option<String>,
59}
60
61struct Complex {
62 steps: Vec<(Comb, Compound)>,
64}
65
66fn parse_selector(input: &str) -> Vec<Complex> {
69 split_groups(input)
70 .iter()
71 .filter_map(|g| parse_complex(g))
72 .collect()
73}
74
75fn split_groups(input: &str) -> Vec<String> {
77 let mut groups = Vec::new();
78 let mut cur = String::new();
79 let mut depth = 0i32;
80 for c in input.chars() {
81 match c {
82 '[' => {
83 depth += 1;
84 cur.push(c);
85 }
86 ']' => {
87 depth -= 1;
88 cur.push(c);
89 }
90 ',' if depth <= 0 => {
91 groups.push(cur.clone());
92 cur.clear();
93 }
94 _ => cur.push(c),
95 }
96 }
97 groups.push(cur);
98 groups
99}
100
101fn parse_complex(group: &str) -> Option<Complex> {
102 let mut steps: Vec<(Comb, Compound)> = Vec::new();
103 let mut chars = group.chars().peekable();
104 let mut comb = Comb::Descendant;
105
106 loop {
107 skip_ws(&mut chars);
108 if matches!(chars.peek(), Some('>')) {
109 chars.next();
110 comb = Comb::Child;
111 skip_ws(&mut chars);
112 }
113
114 let mut buf = String::new();
115 let mut in_bracket = false;
116 while let Some(&c) = chars.peek() {
117 if in_bracket {
118 buf.push(c);
119 chars.next();
120 if c == ']' {
121 in_bracket = false;
122 }
123 } else if c == '[' {
124 in_bracket = true;
125 buf.push(c);
126 chars.next();
127 } else if c.is_whitespace() || c == '>' {
128 break;
129 } else {
130 buf.push(c);
131 chars.next();
132 }
133 }
134
135 if buf.is_empty() {
136 break;
137 }
138 let compound = parse_compound(&buf)?;
139 steps.push((comb, compound));
140 comb = Comb::Descendant;
141 if chars.peek().is_none() {
142 break;
143 }
144 }
145
146 if steps.is_empty() {
147 None
148 } else {
149 Some(Complex { steps })
150 }
151}
152
153fn parse_compound(s: &str) -> Option<Compound> {
154 let mut tag = None;
155 let mut id = None;
156 let mut classes = Vec::new();
157 let mut attrs = Vec::new();
158 let mut chars = s.chars().peekable();
159
160 match chars.peek() {
162 Some('*') => {
163 chars.next();
164 }
165 Some(&c) if is_ident_start(c) => {
166 let mut t = String::new();
167 while let Some(&c) = chars.peek() {
168 if is_ident_char(c) {
169 t.push(c.to_ascii_lowercase());
170 chars.next();
171 } else {
172 break;
173 }
174 }
175 tag = Some(t);
176 }
177 _ => {}
178 }
179
180 while let Some(&c) = chars.peek() {
181 match c {
182 '#' => {
183 chars.next();
184 id = Some(read_ident(&mut chars));
185 }
186 '.' => {
187 chars.next();
188 classes.push(read_ident(&mut chars));
189 }
190 '[' => {
191 chars.next();
192 attrs.push(read_attr(&mut chars));
193 }
194 _ => {
195 chars.next();
196 }
197 }
198 }
199
200 Some(Compound {
201 tag,
202 id,
203 classes,
204 attrs,
205 })
206}
207
208fn read_ident(chars: &mut Peekable<Chars<'_>>) -> String {
209 let mut s = String::new();
210 while let Some(&c) = chars.peek() {
211 if is_ident_char(c) {
212 s.push(c);
213 chars.next();
214 } else {
215 break;
216 }
217 }
218 s
219}
220
221fn read_attr(chars: &mut Peekable<Chars<'_>>) -> AttrPred {
222 let mut inner = String::new();
223 for c in chars.by_ref() {
224 if c == ']' {
225 break;
226 }
227 inner.push(c);
228 }
229 let inner = inner.trim();
230 match inner.find('=') {
231 Some(eq) => {
232 let name = inner.get(..eq).unwrap_or("").trim().to_ascii_lowercase();
233 let mut val = inner.get(eq + 1..).unwrap_or("").trim();
234 val = strip_quotes(val);
235 AttrPred {
236 name,
237 value: Some(val.to_string()),
238 }
239 }
240 None => AttrPred {
241 name: inner.to_ascii_lowercase(),
242 value: None,
243 },
244 }
245}
246
247fn strip_quotes(v: &str) -> &str {
248 let bytes = v.as_bytes();
249 if bytes.len() >= 2 {
250 let first = bytes.first().copied();
251 let last = bytes.last().copied();
252 if (first == Some(b'"') && last == Some(b'"'))
253 || (first == Some(b'\'') && last == Some(b'\''))
254 {
255 return v.get(1..v.len() - 1).unwrap_or(v);
256 }
257 }
258 v
259}
260
261fn skip_ws(chars: &mut Peekable<Chars<'_>>) {
262 while let Some(&c) = chars.peek() {
263 if c.is_whitespace() {
264 chars.next();
265 } else {
266 break;
267 }
268 }
269}
270
271fn is_ident_start(c: char) -> bool {
272 c.is_ascii_alphabetic() || c == '_' || c == '-'
273}
274
275fn is_ident_char(c: char) -> bool {
276 c.is_ascii_alphanumeric() || c == '_' || c == '-'
277}
278
279fn matches_complex(node: NodeRef<'_>, complex: &Complex) -> bool {
282 let n = complex.steps.len();
283 if n == 0 {
284 return false;
285 }
286 matches_from(node, &complex.steps, n - 1)
287}
288
289fn matches_from(node: NodeRef<'_>, steps: &[(Comb, Compound)], i: usize) -> bool {
290 let (comb, compound) = match steps.get(i) {
291 Some(s) => s,
292 None => return false,
293 };
294 if !simple_matches(node, compound) {
295 return false;
296 }
297 if i == 0 {
298 return true;
299 }
300 match comb {
301 Comb::Child => node.parent().is_some_and(|p| matches_from(p, steps, i - 1)),
302 Comb::Descendant => {
303 let mut anc = node.parent();
304 while let Some(a) = anc {
305 if matches_from(a, steps, i - 1) {
306 return true;
307 }
308 anc = a.parent();
309 }
310 false
311 }
312 }
313}
314
315fn simple_matches(node: NodeRef<'_>, c: &Compound) -> bool {
316 if let Some(tag) = &c.tag {
317 match node.tag_name() {
318 Some(t) if t.eq_ignore_ascii_case(tag) => {}
319 _ => return false,
320 }
321 }
322 if let Some(id) = &c.id {
323 if node.attr("id") != Some(id.as_str()) {
324 return false;
325 }
326 }
327 for class in &c.classes {
328 if !node.has_class(class) {
329 return false;
330 }
331 }
332 for attr in &c.attrs {
333 match node.attr(&attr.name) {
334 None => return false,
335 Some(v) => {
336 if let Some(want) = &attr.value {
337 if v != want {
338 return false;
339 }
340 }
341 }
342 }
343 }
344 true
345}