1use std::collections::HashSet;
2
3use crate::dom::element::HTMLElement;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Combinator {
7 Descendant,
8 Child,
9 Adjacent,
10 Sibling,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum AttrOp {
15 Exists,
16 Eq,
17 Prefix,
18 Suffix,
19 Substr,
20 Includes,
21 Dash,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum CaseMode {
26 Sensitive,
27 Insensitive,
28}
29
30#[derive(Debug, Clone)]
31pub struct AttrMatcher {
32 pub name: String,
33 pub op: AttrOp,
34 pub value: String,
35 pub case: CaseMode,
36}
37
38#[derive(Debug, Clone)]
39pub enum NthExpr {
40 Number(i32),
41 Odd,
42 Even,
43 Pattern { a: i32, b: i32 },
44}
45
46#[derive(Debug, Clone)]
47pub enum Pseudo {
48 FirstChild,
49 LastChild,
50 OnlyChild,
51 FirstOfType,
52 LastOfType,
53 OnlyOfType,
54 NthChild(NthExpr),
55 NthLastChild(NthExpr),
56 NthOfType(NthExpr),
57 NthLastOfType(NthExpr),
58 Not(Vec<Selector>),
59 Scope,
60 Is(Vec<Selector>),
61 Where(Vec<Selector>),
62 Has(Vec<Selector>),
63 Empty,
64 Root,
65}
66
67#[derive(Debug, Clone, Default)]
68pub struct CompoundSelector {
69 pub tag: Option<String>,
70 pub id: Option<String>,
71 pub classes: Vec<String>,
72 pub attrs: Vec<AttrMatcher>,
73 pub pseudos: Vec<Pseudo>,
74}
75
76#[derive(Debug, Clone)]
77pub struct Selector(pub Vec<(Option<Combinator>, CompoundSelector)>);
78
79pub fn query_selector_all<'a>(root: &'a HTMLElement, selector_str: &str) -> Vec<&'a HTMLElement> {
80 let selectors = parse_selector_list(selector_str);
81 if selectors.is_empty() {
82 return vec![];
83 }
84
85 let mut ptr_set: HashSet<*const HTMLElement> = HashSet::new();
90 for sel in selectors {
91 for el in apply_selector(root, &sel) {
92 ptr_set.insert(el as *const HTMLElement);
93 }
94 }
95 let mut ordered = Vec::new();
96 collect_in_order_smart(root, &ptr_set, &mut ordered);
97 ordered
98}
99
100fn collect_in_order<'a>(
101 root: &'a HTMLElement,
102 set: &HashSet<*const HTMLElement>,
103 out: &mut Vec<&'a HTMLElement>,
104) {
105 let mut stack = vec![root];
107
108 while let Some(el) = stack.pop() {
109 if !el.is_root() {
110 let p = el as *const HTMLElement;
111 if set.contains(&p) {
112 out.push(el);
113 }
114 }
115
116 let children: Vec<_> = el.iter_elements().collect();
118 for child in children.into_iter().rev() {
119 stack.push(child);
120 }
121 }
122}
123
124#[cfg(feature = "parallel")]
125fn collect_in_order_parallel<'a>(
126 root: &'a HTMLElement,
127 set: &HashSet<*const HTMLElement>,
128) -> Vec<&'a HTMLElement> {
129 let mut results = Vec::new();
132 collect_in_order(root, set, &mut results);
133 results
134}
135
136fn collect_in_order_smart<'a>(
138 root: &'a HTMLElement,
139 set: &HashSet<*const HTMLElement>,
140 out: &mut Vec<&'a HTMLElement>,
141) {
142 #[cfg(feature = "parallel")]
143 {
144 let (width, depth) = estimate_dom_dimensions(root);
146 const PARALLEL_WIDTH_THRESHOLD: usize = 20;
147 const PARALLEL_DEPTH_THRESHOLD: usize = 5;
148
149 if width >= PARALLEL_WIDTH_THRESHOLD && depth <= PARALLEL_DEPTH_THRESHOLD {
151 let parallel_results = collect_in_order_parallel(root, set);
152 out.extend(parallel_results);
153 return;
154 }
155 }
156
157 collect_in_order(root, set, out);
159}
160
161#[cfg(feature = "parallel")]
162fn estimate_dom_dimensions(root: &HTMLElement) -> (usize, usize) {
163 let mut max_width = 0;
165 let mut depth = 0;
166 let mut current_level = vec![root];
167
168 while !current_level.is_empty() && depth < 10 {
169 max_width = max_width.max(current_level.len());
171 let next_level: Vec<_> = current_level
172 .iter()
173 .flat_map(|el| el.iter_elements())
174 .collect();
175 current_level = next_level;
176 depth += 1;
177 }
178
179 (max_width, depth)
180}
181
182fn apply_selector<'a>(root: &'a HTMLElement, selector: &Selector) -> Vec<&'a HTMLElement> {
183 let mut current: Vec<&HTMLElement> = vec![root];
184 for (idx, (comb, comp)) in selector.0.iter().enumerate() {
185 let mut next_vec: Vec<&HTMLElement> = Vec::new();
186 for base in ¤t {
187 let effective = comb.unwrap_or(Combinator::Descendant);
188 match effective {
189 Combinator::Descendant => {
190 if idx == 0 {
191 if match_compound(root, base, comp) {
192 next_vec.push(base);
193 }
194 }
195 collect_descendants(root, base, comp, &mut next_vec);
196 }
197 Combinator::Child => {
198 for child in base.iter_elements() {
199 if match_compound(root, child, comp) {
200 next_vec.push(child);
201 }
202 }
203 }
204 Combinator::Adjacent => {
205 if let Some(parent) = find_parent(root, base) {
206 let sibs: Vec<&HTMLElement> = parent.iter_elements().collect();
207 if let Some(pos) = sibs.iter().position(|e| std::ptr::eq(*e, *base)) {
208 if pos + 1 < sibs.len() {
209 let n = sibs[pos + 1];
210 if match_compound(root, n, comp) {
211 next_vec.push(n);
212 }
213 }
214 }
215 }
216 }
217 Combinator::Sibling => {
218 if let Some(parent) = find_parent(root, base) {
219 let sibs: Vec<&HTMLElement> = parent.iter_elements().collect();
220 if let Some(pos) = sibs.iter().position(|e| std::ptr::eq(*e, *base)) {
221 for n in sibs.iter().skip(pos + 1) {
222 if match_compound(root, *n, comp) {
223 next_vec.push(*n);
224 }
225 }
226 }
227 }
228 }
229 }
230 }
231 let mut seen: HashSet<*const HTMLElement> = HashSet::new();
232 let mut dedup = Vec::new();
233 for e in next_vec {
234 let p = e as *const HTMLElement;
235 if seen.insert(p) {
236 dedup.push(e);
237 }
238 }
239 current = dedup;
240 }
241 current.into_iter().filter(|e| !e.is_root()).collect()
242}
243
244fn collect_descendants<'a>(
245 root: &'a HTMLElement,
246 el: &'a HTMLElement,
247 comp: &CompoundSelector,
248 out: &mut Vec<&'a HTMLElement>,
249) {
250 for child in el.iter_elements() {
251 if match_compound(root, child, comp) {
252 out.push(child);
253 }
254 collect_descendants(root, child, comp, out);
255 }
256}
257
258fn match_compound<'a>(root: &'a HTMLElement, el: &'a HTMLElement, comp: &CompoundSelector) -> bool {
259 if let Some(tag) = &comp.tag {
260 if !el.name().eq_ignore_ascii_case(tag) {
261 return false;
262 }
263 }
264 if let Some(id) = &comp.id {
265 match el.get_attr("id") {
266 Some(v) if v == id => {}
267 _ => return false,
268 }
269 }
270 for class in &comp.classes {
271 if !el.class_list_view().iter().any(|c| c == class) {
272 return false;
273 }
274 }
275 if !comp.attrs.is_empty() {
276 for matcher in &comp.attrs {
277 let mut_ptr = el as *const HTMLElement as *mut HTMLElement;
280 unsafe {
281 (*mut_ptr).ensure_all_attrs();
282 }
283
284 let raw_opt = unsafe {
286 (*mut_ptr).attrs.iter().find_map(|(k, v)| {
287 if k.eq_ignore_ascii_case(&matcher.name) {
288 Some(v.as_str())
289 } else {
290 None
291 }
292 })
293 };
294
295 match matcher.op {
296 AttrOp::Exists => {
297 if raw_opt.is_none() {
298 return false;
299 }
300 }
301 _ => {
302 if raw_opt.is_none() {
303 return false;
304 }
305 let val = raw_opt.unwrap();
306 let (left, right) = match matcher.case {
307 CaseMode::Insensitive => {
308 (val.to_ascii_lowercase(), matcher.value.to_ascii_lowercase())
309 }
310 CaseMode::Sensitive => (val.to_string(), matcher.value.clone()),
311 };
312 let ok = match matcher.op {
313 AttrOp::Exists => true,
314 AttrOp::Eq => left == right,
315 AttrOp::Prefix => left.starts_with(&right),
316 AttrOp::Suffix => left.ends_with(&right),
317 AttrOp::Substr => left.contains(&right),
318 AttrOp::Includes => left.split_whitespace().any(|t| t == right),
319 AttrOp::Dash => left == right || left.starts_with(&(right + "-")),
320 };
321 if !ok {
322 return false;
323 }
324 }
325 }
326 }
327 }
328 if !comp.pseudos.is_empty() {
329 for p in &comp.pseudos {
330 if !match_pseudo(root, el, p) {
331 return false;
332 }
333 }
334 }
335 true
336}
337
338fn match_pseudo<'a>(root: &'a HTMLElement, el: &'a HTMLElement, pseudo: &Pseudo) -> bool {
339 match pseudo {
340 Pseudo::FirstChild => position_in_parent(root, el)
341 .map(|(i, _, _, _)| i == 0)
342 .unwrap_or(false),
343 Pseudo::LastChild => position_in_parent(root, el)
344 .map(|(i, len, _, _)| i + 1 == len)
345 .unwrap_or(false),
346 Pseudo::OnlyChild => position_in_parent(root, el)
347 .map(|(_, len, _, _)| len == 1)
348 .unwrap_or(false),
349 Pseudo::FirstOfType => position_in_parent(root, el)
350 .map(|(_, _, ti, _)| ti == 0)
351 .unwrap_or(false),
352 Pseudo::LastOfType => position_in_parent(root, el)
353 .map(|(_, _, ti, tlen)| ti + 1 == tlen)
354 .unwrap_or(false),
355 Pseudo::OnlyOfType => position_in_parent(root, el)
356 .map(|(_, _, _, tlen)| tlen == 1)
357 .unwrap_or(false),
358 Pseudo::NthChild(expr) => position_in_parent(root, el)
359 .map(|(i, _, _, _)| match_nth(expr, i as i32 + 1))
360 .unwrap_or(false),
361 Pseudo::NthLastChild(expr) => position_in_parent(root, el)
362 .map(|(i, len, _, _)| {
363 let rev = (len - i - 1) as i32 + 1;
364 match_nth(expr, rev)
365 })
366 .unwrap_or(false),
367 Pseudo::NthOfType(expr) => position_in_parent(root, el)
368 .map(|(_, _, ti, _)| match_nth(expr, ti as i32 + 1))
369 .unwrap_or(false),
370 Pseudo::NthLastOfType(expr) => position_in_parent(root, el)
371 .map(|(_, _, ti, tlen)| {
372 let rev = (tlen - ti - 1) as i32 + 1;
373 match_nth(expr, rev)
374 })
375 .unwrap_or(false),
376 Pseudo::Not(list) => !list.iter().any(|sel| apply_selector_from_el(root, el, sel)),
377 Pseudo::Empty => {
378 el.iter_elements().next().is_none()
379 && el
380 .children
381 .iter()
382 .all(|n| n.as_element().is_some() || n.text().trim().is_empty())
383 }
384 Pseudo::Root => find_parent(root, el).map(|p| p.is_root()).unwrap_or(false),
385 Pseudo::Is(list) => list.iter().any(|sel| apply_selector_from_el(root, el, sel)),
386 Pseudo::Where(list) => list.iter().any(|sel| apply_selector_from_el(root, el, sel)),
387 Pseudo::Has(list) => {
388 fn any_desc<'a>(
390 cur: &'a HTMLElement,
391 root: &'a HTMLElement,
392 list: &Vec<Selector>,
393 ) -> bool {
394 for child in cur.iter_elements() {
395 for sel in list {
396 if apply_selector_from_el(root, child, sel) {
397 return true;
398 }
399 }
400 if any_desc(child, root, list) {
401 return true;
402 }
403 }
404 false
405 }
406 any_desc(el, root, list)
407 }
408 Pseudo::Scope => true,
409 }
410}
411
412fn match_nth(expr: &NthExpr, index_one: i32) -> bool {
413 match expr {
414 NthExpr::Number(n) => index_one == *n,
415 NthExpr::Odd => (index_one % 2) == 1,
416 NthExpr::Even => (index_one % 2) == 0,
417 NthExpr::Pattern { a, b } => {
418 let a = *a;
419 let b = *b;
420 if a == 0 {
421 return index_one == b;
422 }
423 if a > 0 {
424 if index_one < b {
425 return false;
426 }
427 (index_one - b) % a == 0
428 } else {
429 let mut k = 0;
430 loop {
431 let val = a * k + b;
432 if val == index_one {
433 return true;
434 }
435 if val < 1 {
436 return false;
437 }
438 if val < index_one {
439 k += 1;
440 continue;
441 }
442 return false;
443 }
444 }
445 }
446 }
447}
448
449fn position_in_parent<'a>(
450 root: &'a HTMLElement,
451 el: &'a HTMLElement,
452) -> Option<(usize, usize, usize, usize)> {
453 let parent = find_parent(root, el)?;
454 let list: Vec<&HTMLElement> = parent.iter_elements().collect();
455 if list.is_empty() {
456 return None;
457 }
458 let same: Vec<&HTMLElement> = list
459 .iter()
460 .copied()
461 .filter(|c| c.name() == el.name())
462 .collect();
463 let idx = list.iter().position(|e| std::ptr::eq(*e, el))?;
464 let tidx = same.iter().position(|e| std::ptr::eq(*e, el))?;
465 Some((idx, list.len(), tidx, same.len()))
466}
467
468fn find_parent<'a>(root: &'a HTMLElement, target: &'a HTMLElement) -> Option<&'a HTMLElement> {
469 if std::ptr::eq(root, target) {
470 return None;
471 }
472 fn dfs<'a>(cur: &'a HTMLElement, target: &'a HTMLElement) -> Option<&'a HTMLElement> {
473 for child in cur.iter_elements() {
474 if std::ptr::eq(child, target) {
475 return Some(cur);
476 }
477 if let Some(p) = dfs(child, target) {
478 return Some(p);
479 }
480 }
481 None
482 }
483 dfs(root, target)
484}
485
486fn parse_selector_list(input: &str) -> Vec<Selector> {
487 let mut parts = Vec::new();
488 let mut buf = String::new();
489 let mut depth = 0; for c in input.chars() {
491 match c {
492 '(' => {
493 depth += 1;
494 buf.push(c);
495 }
496 ')' => {
497 if depth > 0 {
498 depth -= 1;
499 }
500 buf.push(c);
501 }
502 ',' if depth == 0 => {
503 let t = buf.trim();
504 if !t.is_empty() {
505 parts.push(t.to_string());
506 }
507 buf.clear();
508 }
509 _ => buf.push(c),
510 }
511 }
512 let t = buf.trim();
513 if !t.is_empty() {
514 parts.push(t.to_string());
515 }
516 parts
517 .into_iter()
518 .filter_map(|p| {
519 let tt = p.trim();
520 if tt.is_empty() {
521 None
522 } else {
523 Some(parse_single_selector(tt))
524 }
525 })
526 .collect()
527}
528
529fn parse_single_selector(input: &str) -> Selector {
530 let mut chars = input.chars().peekable();
531 let mut parts: Vec<(Option<Combinator>, CompoundSelector)> = Vec::new();
532 let mut current = CompoundSelector::default();
533 let mut pending: Option<Combinator> = None;
534 while let Some(&ch) = chars.peek() {
535 match ch {
536 ' ' | '\t' | '\n' | '\r' => {
537 chars.next();
538 if !current_is_empty(¤t) {
539 parts.push((pending.take(), current));
540 current = CompoundSelector::default();
541 }
542 pending.get_or_insert(Combinator::Descendant);
543 while let Some(&c2) = chars.peek() {
544 if c2.is_whitespace() {
545 chars.next();
546 } else {
547 break;
548 }
549 }
550 }
551 '>' => {
552 chars.next();
553 if !current_is_empty(¤t) {
554 parts.push((pending.take(), current));
555 current = CompoundSelector::default();
556 }
557 pending = Some(Combinator::Child);
558 skip_ws(&mut chars);
559 }
560 '+' => {
561 chars.next();
562 if !current_is_empty(¤t) {
563 parts.push((pending.take(), current));
564 current = CompoundSelector::default();
565 }
566 pending = Some(Combinator::Adjacent);
567 skip_ws(&mut chars);
568 }
569 '~' => {
570 chars.next();
571 if !current_is_empty(¤t) {
572 parts.push((pending.take(), current));
573 current = CompoundSelector::default();
574 }
575 pending = Some(Combinator::Sibling);
576 skip_ws(&mut chars);
577 }
578 '#' => {
579 chars.next();
580 let ident = read_ident(&mut chars);
581 current.id = Some(ident);
582 }
583 '.' => {
584 chars.next();
585 let ident = read_ident(&mut chars);
586 current.classes.push(ident);
587 }
588 '[' => {
589 chars.next();
590 let name = read_ident(&mut chars);
591 skip_ws(&mut chars);
592 let mut op = AttrOp::Exists;
593 let mut value = String::new();
594 let mut case = CaseMode::Sensitive;
595 if let Some(&c2) = chars.peek() {
596 match c2 {
597 '=' => {
598 op = AttrOp::Eq;
599 chars.next();
600 skip_ws(&mut chars);
601 value = read_attr_value(&mut chars);
602 }
603 '^' | '$' | '*' | '~' | '|' => {
604 let first = c2;
605 chars.next();
606 if matches!(chars.peek(), Some('=')) {
607 chars.next();
608 op = match first {
609 '^' => AttrOp::Prefix,
610 '$' => AttrOp::Suffix,
611 '*' => AttrOp::Substr,
612 '~' => AttrOp::Includes,
613 '|' => AttrOp::Dash,
614 _ => AttrOp::Eq,
615 };
616 skip_ws(&mut chars);
617 value = read_attr_value(&mut chars);
618 }
619 }
620 _ => {}
621 }
622 }
623 skip_ws(&mut chars);
624 if let Some(&flag) = chars.peek() {
625 if flag == 'i' {
626 case = CaseMode::Insensitive;
627 chars.next();
628 } else if flag == 's' {
629 case = CaseMode::Sensitive;
630 chars.next();
631 }
632 }
633 while let Some(c) = chars.next() {
634 if c == ']' {
635 break;
636 }
637 }
638 current.attrs.push(AttrMatcher {
639 name: name.to_lowercase(),
640 op,
641 value,
642 case,
643 });
644 }
645 '*' => {
646 chars.next();
647 }
648 ':' => {
649 chars.next();
650 let pseudo_name = read_ident(&mut chars).to_ascii_lowercase();
651 let pseudo = if matches!(chars.peek(), Some('(')) {
652 chars.next();
653 let arg = read_until_paren(&mut chars);
654 parse_pseudo_with_arg(&pseudo_name, &arg)
655 } else {
656 parse_pseudo_no_arg(&pseudo_name)
657 };
658 if let Some(p) = pseudo {
659 current.pseudos.push(p);
660 }
661 }
662 _ => {
663 if current.tag.is_none() {
664 let ident = read_ident_starting(ch, &mut chars);
665 current.tag = Some(ident.to_lowercase());
666 } else {
667 chars.next();
668 }
669 }
670 }
671 }
672 if !current_is_empty(¤t) || parts.is_empty() {
673 parts.push((pending.take(), current));
674 }
675 Selector(parts)
676}
677
678fn current_is_empty(c: &CompoundSelector) -> bool {
679 c.tag.is_none()
680 && c.id.is_none()
681 && c.classes.is_empty()
682 && c.attrs.is_empty()
683 && c.pseudos.is_empty()
684}
685fn skip_ws<I: Iterator<Item = char>>(it: &mut std::iter::Peekable<I>) {
686 while matches!(it.peek(), Some(c) if c.is_whitespace()) {
687 it.next();
688 }
689}
690fn read_ident<I: Iterator<Item = char>>(it: &mut std::iter::Peekable<I>) -> String {
691 let mut s = String::new();
692 while let Some(&c) = it.peek() {
693 if c.is_alphanumeric() || c == '-' || c == '_' {
694 s.push(c);
695 it.next();
696 } else {
697 break;
698 }
699 }
700 s
701}
702fn read_ident_starting(first: char, it: &mut std::iter::Peekable<std::str::Chars<'_>>) -> String {
703 let mut s = String::new();
704 s.push(first);
705 it.next();
706 while let Some(&c) = it.peek() {
707 if c.is_alphanumeric() || c == '-' || c == '_' {
708 s.push(c);
709 it.next();
710 } else {
711 break;
712 }
713 }
714 s
715}
716fn read_attr_value<I: Iterator<Item = char>>(it: &mut std::iter::Peekable<I>) -> String {
717 if let Some(&q) = it.peek() {
718 if q == '"' || q == '\'' {
719 it.next();
720 let mut val = String::new();
721 while let Some(&c) = it.peek() {
722 it.next();
723 if c == q {
724 break;
725 }
726 val.push(c);
727 }
728 return val;
729 }
730 }
731 read_ident(it)
732}
733fn read_until_paren<I: Iterator<Item = char>>(it: &mut std::iter::Peekable<I>) -> String {
734 let mut depth = 1;
735 let mut s = String::new();
736 while let Some(&c) = it.peek() {
737 it.next();
738 if c == '(' {
739 depth += 1;
740 } else if c == ')' {
741 depth -= 1;
742 if depth == 0 {
743 break;
744 }
745 }
746 s.push(c);
747 }
748 s.trim().to_string()
749}
750fn parse_pseudo_no_arg(name: &str) -> Option<Pseudo> {
751 Some(match name {
752 "first-child" => Pseudo::FirstChild,
753 "last-child" => Pseudo::LastChild,
754 "only-child" => Pseudo::OnlyChild,
755 "first-of-type" => Pseudo::FirstOfType,
756 "last-of-type" => Pseudo::LastOfType,
757 "only-of-type" => Pseudo::OnlyOfType,
758 "empty" => Pseudo::Empty,
759 "root" => Pseudo::Root,
760 "scope" => Pseudo::Scope,
761 _ => return None,
762 })
763}
764fn parse_pseudo_with_arg(name: &str, arg: &str) -> Option<Pseudo> {
765 match name {
766 "nth-child" => Some(Pseudo::NthChild(parse_nth_expr(arg)?)),
767 "nth-last-child" => Some(Pseudo::NthLastChild(parse_nth_expr(arg)?)),
768 "nth-of-type" => Some(Pseudo::NthOfType(parse_nth_expr(arg)?)),
769 "nth-last-of-type" => Some(Pseudo::NthLastOfType(parse_nth_expr(arg)?)),
770 "not" => {
771 let sels = parse_selector_list(arg);
772 if sels.is_empty() {
773 None
774 } else {
775 Some(Pseudo::Not(sels))
776 }
777 }
778 "is" => {
779 let sels = parse_selector_list(arg);
780 if sels.is_empty() {
781 None
782 } else {
783 Some(Pseudo::Is(sels))
784 }
785 }
786 "where" => {
787 let sels = parse_selector_list(arg);
788 if sels.is_empty() {
789 None
790 } else {
791 Some(Pseudo::Where(sels))
792 }
793 }
794 "has" => {
795 let sels = parse_selector_list(arg);
796 if sels.is_empty() {
797 None
798 } else {
799 Some(Pseudo::Has(sels))
800 }
801 }
802 _ => None,
803 }
804}
805fn parse_nth_expr(s: &str) -> Option<NthExpr> {
806 let t = s.trim().to_ascii_lowercase();
807 if t == "odd" {
808 return Some(NthExpr::Odd);
809 }
810 if t == "even" {
811 return Some(NthExpr::Even);
812 }
813 if let Ok(n) = t.parse::<i32>() {
814 if n > 0 {
815 return Some(NthExpr::Number(n));
816 }
817 }
818 if let Some(pos) = t.find('n') {
819 let (a_part, rest) = t.split_at(pos);
820 let rest = &rest[1..];
821 let a = if a_part.is_empty() || a_part == "+" {
822 1
823 } else if a_part == "-" {
824 -1
825 } else {
826 a_part.parse().ok()?
827 };
828 let b = if rest.is_empty() {
829 0
830 } else {
831 let r = rest.trim();
832 if r.starts_with('+') {
833 r[1..].parse().ok()?
834 } else {
835 r.parse().ok()?
836 }
837 };
838 return Some(NthExpr::Pattern { a, b });
839 }
840 None
841}
842
843pub fn apply_selector_from_el<'a>(
844 root: &'a HTMLElement,
845 el: &'a HTMLElement,
846 selector: &Selector,
847) -> bool {
848 let matches = apply_selector(root, selector);
849 matches.into_iter().any(|e| std::ptr::eq(e, el))
850}
851pub fn parse_selector_list_public(input: &str) -> Vec<Selector> {
852 parse_selector_list(input)
853}
854
855pub fn selector_parts(sel: &Selector) -> &Vec<(Option<Combinator>, CompoundSelector)> {
857 &sel.0
858}