1use std::fmt;
23use std::str::FromStr;
24
25use crate::{Descendants, Element, Html, NodeId, NodeRef, Tag};
26
27const MAX_NESTING: u32 = 32;
29const MAX_DEPTH: u32 = 256;
31
32#[derive(Debug, Clone)]
34pub struct Selector {
35 list: Box<[Complex]>,
36}
37
38#[derive(Debug, Clone)]
41struct Complex {
42 parts: Box<[Compound]>,
43}
44
45#[derive(Debug, Clone)]
46struct Compound {
47 tag: Option<Tag>,
49 simples: Box<[Simple]>,
50 comb: Comb,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54enum Comb {
55 Descendant,
56 Child,
57 Next,
58 Later,
59}
60
61#[derive(Debug, Clone)]
62enum Simple {
63 Type(Tag, Box<str>),
65 Id(Box<str>),
66 Class(Box<str>),
67 Attr(Box<AttrSel>),
68 Nth(Box<Nth>),
69 Empty,
70 Root,
71 Scope,
72 Anchor,
74 Not(Selector),
75 Is(Selector),
76 Has(Selector),
77}
78
79impl Simple {
80 fn cost(&self) -> u8 {
82 match self {
83 Simple::Type(..) => 0,
84 Simple::Id(_) => 1,
85 Simple::Class(_) => 2,
86 Simple::Attr(_) => 3,
87 Simple::Empty | Simple::Root | Simple::Scope | Simple::Anchor => 4,
88 Simple::Nth(_) => 5,
89 Simple::Not(_) | Simple::Is(_) => 6,
90 Simple::Has(_) => 7,
91 }
92 }
93}
94
95#[derive(Debug, Clone)]
96struct AttrSel {
97 name: Box<str>,
98 op: AttrOp,
99 value: Box<str>,
100 ci: bool,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105enum AttrOp {
106 Exists,
107 Equals,
108 Includes,
109 Dash,
110 Prefix,
111 Suffix,
112 Substring,
113 Never,
115}
116
117#[derive(Debug, Clone)]
119struct Nth {
120 a: i32,
121 b: i32,
122 last: bool,
124 of_type: bool,
126 of: Option<Selector>,
128}
129
130impl Nth {
131 fn simple(a: i32, b: i32, last: bool, of_type: bool) -> Simple {
132 Simple::Nth(Box::new(Nth {
133 a,
134 b,
135 last,
136 of_type,
137 of: None,
138 }))
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct SelectorError {
145 offset: usize,
146 message: String,
147}
148
149impl SelectorError {
150 pub fn offset(&self) -> usize {
152 self.offset
153 }
154
155 pub fn message(&self) -> &str {
156 &self.message
157 }
158}
159
160impl fmt::Display for SelectorError {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 write!(f, "{} at offset {}", self.message, self.offset)
163 }
164}
165
166impl std::error::Error for SelectorError {}
167
168impl Selector {
169 pub fn parse(s: &str) -> Result<Selector, SelectorError> {
171 let mut p = Parser {
172 s,
173 b: s.as_bytes(),
174 pos: 0,
175 nesting: 0,
176 };
177 let (sel, depth) = p.list(false, false)?;
178 if p.pos < p.b.len() {
179 return Err(p.unexpected());
180 }
181 if depth > MAX_DEPTH {
182 return Err(SelectorError {
183 offset: 0,
184 message: format!("selector is too complex (matching depth {depth} > {MAX_DEPTH})"),
185 });
186 }
187 Ok(sel)
188 }
189
190 pub fn matches(&self, node: NodeRef<'_>) -> bool {
192 self.matches_with_scope(node, None)
193 }
194
195 pub fn matches_with_scope(&self, node: NodeRef<'_>, scope: Option<NodeRef<'_>>) -> bool {
197 let cx = Ctx {
198 scope: scope.filter(NodeRef::is_element).map(|s| s.id()),
199 anchor: None,
200 };
201 node.is_element() && self.matches_in(node, &cx)
202 }
203
204 #[inline]
205 fn matches_in(&self, el: NodeRef<'_>, cx: &Ctx) -> bool {
206 self.list.iter().any(|c| {
207 c.parts[0].tag_may_match(el) && match_complex(&c.parts, 0, el, cx) == Res::Matched
208 })
209 }
210}
211
212impl FromStr for Selector {
213 type Err = SelectorError;
214 fn from_str(s: &str) -> Result<Self, Self::Err> {
215 Selector::parse(s)
216 }
217}
218
219impl<'a> Html<'a> {
220 pub fn select<'s>(&'a self, sel: &'s Selector) -> Select<'a, 's> {
222 Select {
223 inner: self.root().descendants(),
224 sel,
225 cx: Ctx {
226 scope: None,
227 anchor: None,
228 },
229 }
230 }
231}
232
233impl<'a> NodeRef<'a> {
234 pub fn select<'s>(&self, sel: &'s Selector) -> Select<'a, 's> {
236 let mut inner = self.descendants();
237 inner.next();
238 Select {
239 inner,
240 sel,
241 cx: Ctx {
242 scope: self.is_element().then(|| self.id()),
243 anchor: None,
244 },
245 }
246 }
247}
248
249pub struct Select<'a, 's> {
251 inner: Descendants<'a>,
252 sel: &'s Selector,
253 cx: Ctx,
254}
255
256impl<'a> Iterator for Select<'a, '_> {
257 type Item = NodeRef<'a>;
258 fn next(&mut self) -> Option<NodeRef<'a>> {
259 self.inner
260 .by_ref()
261 .find(|n| n.is_element() && self.sel.matches_in(*n, &self.cx))
262 }
263}
264
265struct Ctx {
270 scope: Option<NodeId>,
272 anchor: Option<NodeId>,
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
279enum Res {
280 Matched,
281 RestartFromLaterSibling,
282 RestartFromDescendant,
283 NotMatchedGlobally,
284}
285
286fn match_complex(parts: &[Compound], i: usize, el: NodeRef<'_>, cx: &Ctx) -> Res {
287 let part = &parts[i];
288 if !part.tag_may_match(el) || !match_compound(&part.simples, el, cx) {
289 return Res::RestartFromLaterSibling;
290 }
291 if i + 1 == parts.len() {
292 return Res::Matched;
293 }
294 let comb = part.comb;
295 let not_found = match comb {
296 Comb::Next | Comb::Later => Res::RestartFromDescendant,
297 Comb::Child | Comb::Descendant => Res::NotMatchedGlobally,
298 };
299 let mut cur = el;
300 loop {
301 let next = match comb {
302 Comb::Child | Comb::Descendant => cur.parent().filter(NodeRef::is_element),
303 Comb::Next | Comb::Later => prev_element(cur),
304 };
305 let Some(next) = next else {
306 return not_found;
307 };
308 let r = if parts[i + 1].tag_may_match(next) {
310 match_complex(parts, i + 1, next, cx)
311 } else {
312 Res::RestartFromLaterSibling
313 };
314 match (r, comb) {
315 (Res::Matched | Res::NotMatchedGlobally, _) | (_, Comb::Next) => return r,
316 (_, Comb::Child) => return Res::RestartFromDescendant,
317 (Res::RestartFromDescendant, Comb::Later) => return r,
318 _ => {}
319 }
320 cur = next;
321 }
322}
323
324impl Compound {
325 #[inline]
327 fn tag_may_match(&self, el: NodeRef<'_>) -> bool {
328 match self.tag {
329 Some(t) => matches!(el.tag(), Some(x) if x == t || x == Tag::Other),
330 None => true,
331 }
332 }
333}
334
335fn match_compound(simples: &[Simple], el: NodeRef<'_>, cx: &Ctx) -> bool {
336 let Some(e) = el.as_element() else {
337 return false;
338 };
339 simples.iter().all(|s| match_simple(s, el, &e, cx))
340}
341
342fn match_simple(s: &Simple, el: NodeRef<'_>, e: &Element<'_>, cx: &Ctx) -> bool {
343 match s {
344 Simple::Type(tag, name) => type_matches(e, *tag, name),
345 Simple::Id(id) => e.attr("id") == Some(&**id),
346 Simple::Class(c) => e
347 .attr("class")
348 .is_some_and(|v| v.split_ascii_whitespace().any(|x| x == &**c)),
349 Simple::Attr(a) => e.attr(&a.name).is_some_and(|v| attr_matches(a, v)),
350 Simple::Nth(n) => nth_matches(n, el, e, cx),
351 Simple::Empty => !el.children().any(|c| c.is_element() || c.is_text()),
352 Simple::Root => el.parent().is_some_and(|p| p.is_document()),
353 Simple::Scope => match cx.scope {
354 Some(id) => el.id() == id,
355 None => el.parent().is_some_and(|p| p.is_document()),
356 },
357 Simple::Anchor => cx.anchor == Some(el.id()),
358 Simple::Not(sel) => !sel.matches_in(el, cx),
359 Simple::Is(sel) => sel.matches_in(el, cx),
360 Simple::Has(sel) => has_matches(sel, el, cx),
361 }
362}
363
364fn type_matches(e: &Element<'_>, tag: Tag, name: &str) -> bool {
365 let t = e.tag();
366 if t == tag {
367 tag != Tag::Other || e.name() == name
368 } else {
369 t == Tag::Other && e.name() == name
371 }
372}
373
374fn attr_matches(a: &AttrSel, v: &str) -> bool {
375 let (v, want) = (v.as_bytes(), a.value.as_bytes());
376 let eq = |x: &[u8], y: &[u8]| {
377 if a.ci {
378 x.eq_ignore_ascii_case(y)
379 } else {
380 x == y
381 }
382 };
383 match a.op {
384 AttrOp::Exists => true,
385 AttrOp::Equals => eq(v, want),
386 AttrOp::Includes => v
387 .split(|c| matches!(c, b' ' | b'\t' | b'\n' | b'\x0c' | b'\r'))
388 .any(|w| eq(w, want)),
389 AttrOp::Dash => {
390 eq(v, want)
391 || (v.len() > want.len() && v[want.len()] == b'-' && eq(&v[..want.len()], want))
392 }
393 AttrOp::Prefix => v.len() >= want.len() && eq(&v[..want.len()], want),
394 AttrOp::Suffix => v.len() >= want.len() && eq(&v[v.len() - want.len()..], want),
395 AttrOp::Substring => {
396 if a.ci {
397 v.windows(want.len()).any(|w| w.eq_ignore_ascii_case(want))
398 } else {
399 memchr::memmem::find(v, want).is_some()
400 }
401 }
402 AttrOp::Never => false,
403 }
404}
405
406fn nth_matches(n: &Nth, el: NodeRef<'_>, e: &Element<'_>, cx: &Ctx) -> bool {
407 if let Some(of) = &n.of
408 && !of.matches_in(el, cx)
409 {
410 return false;
411 }
412 let tag = e.tag();
413 let counts = |s: NodeRef<'_>| {
414 let Some(st) = s.tag() else {
415 return false;
416 };
417 if n.of_type {
418 st == tag
419 && (tag != Tag::Other || s.as_element().is_some_and(|se| se.name() == e.name()))
420 } else if let Some(of) = &n.of {
421 of.matches_in(s, cx)
422 } else {
423 true
424 }
425 };
426 let mut pos = 1i64;
427 let mut cur = el;
428 loop {
429 let next = if n.last {
430 cur.next_sibling()
431 } else {
432 cur.prev_sibling()
433 };
434 let Some(s) = next else { break };
435 if counts(s) {
436 pos += 1;
437 }
438 cur = s;
439 }
440 let (a, b) = (i64::from(n.a), i64::from(n.b));
441 if a == 0 {
442 pos == b
443 } else {
444 let d = pos - b;
445 d % a == 0 && d / a >= 0
446 }
447}
448
449fn has_matches(sel: &Selector, el: NodeRef<'_>, cx: &Ctx) -> bool {
451 let inner = Ctx {
452 scope: cx.scope,
453 anchor: Some(el.id()),
454 };
455 sel.list.iter().any(|c| {
456 let leading = c.parts[c.parts.len() - 2].comb;
457 let hit = |n: NodeRef<'_>| {
458 n.is_element() && match_complex(&c.parts, 0, n, &inner) == Res::Matched
459 };
460 let single = c.parts.len() == 2;
462 match leading {
463 Comb::Child if single => el.children().any(hit),
464 Comb::Next if single => next_element(el).is_some_and(hit),
465 Comb::Later if single => {
466 let mut s = el.next_sibling();
467 while let Some(n) = s {
468 if hit(n) {
469 return true;
470 }
471 s = n.next_sibling();
472 }
473 false
474 }
475 Comb::Descendant | Comb::Child => el.descendants().skip(1).any(hit),
476 Comb::Next | Comb::Later => {
477 let mut s = el.next_sibling();
478 while let Some(n) = s {
479 if n.descendants().any(hit) {
480 return true;
481 }
482 s = n.next_sibling();
483 }
484 false
485 }
486 }
487 })
488}
489
490fn next_element<'a>(n: NodeRef<'a>) -> Option<NodeRef<'a>> {
491 let mut cur = n.next_sibling();
492 while let Some(c) = cur {
493 if c.is_element() {
494 return Some(c);
495 }
496 cur = c.next_sibling();
497 }
498 None
499}
500
501fn prev_element<'a>(n: NodeRef<'a>) -> Option<NodeRef<'a>> {
502 let mut cur = n.prev_sibling();
503 while let Some(c) = cur {
504 if c.is_element() {
505 return Some(c);
506 }
507 cur = c.prev_sibling();
508 }
509 None
510}
511
512const CASE_INSENSITIVE_ATTRS: &[&str] = &[
518 "accept",
519 "accept-charset",
520 "align",
521 "alink",
522 "axis",
523 "bgcolor",
524 "charset",
525 "checked",
526 "clear",
527 "codetype",
528 "color",
529 "compact",
530 "declare",
531 "defer",
532 "dir",
533 "direction",
534 "disabled",
535 "enctype",
536 "face",
537 "frame",
538 "hreflang",
539 "http-equiv",
540 "lang",
541 "language",
542 "link",
543 "media",
544 "method",
545 "multiple",
546 "nohref",
547 "noresize",
548 "noshade",
549 "nowrap",
550 "readonly",
551 "rel",
552 "rev",
553 "rules",
554 "scope",
555 "scrolling",
556 "selected",
557 "shape",
558 "target",
559 "text",
560 "type",
561 "valign",
562 "valuetype",
563 "vlink",
564];
565
566struct Parser<'a> {
567 s: &'a str,
568 b: &'a [u8],
569 pos: usize,
570 nesting: u32,
571}
572
573fn is_ws(c: u8) -> bool {
574 matches!(c, b' ' | b'\t' | b'\n' | b'\r' | b'\x0c')
575}
576
577fn is_name_start(c: u8) -> bool {
578 c.is_ascii_alphabetic() || c == b'_' || c >= 0x80
579}
580
581fn is_name(c: u8) -> bool {
582 is_name_start(c) || c.is_ascii_digit() || c == b'-'
583}
584
585impl Parser<'_> {
586 fn peek(&self) -> Option<u8> {
587 self.b.get(self.pos).copied()
588 }
589
590 fn peek_at(&self, i: usize) -> Option<u8> {
591 self.b.get(self.pos + i).copied()
592 }
593
594 fn err(&self, message: impl Into<String>) -> SelectorError {
595 SelectorError {
596 offset: self.pos,
597 message: message.into(),
598 }
599 }
600
601 fn unexpected(&self) -> SelectorError {
602 match self.s[self.pos..].chars().next() {
603 Some(c) => self.err(format!("unexpected `{c}`")),
604 None => self.err("unexpected end of selector"),
605 }
606 }
607
608 fn skip_ws(&mut self) -> bool {
610 let mut ws = false;
611 loop {
612 match self.peek() {
613 Some(c) if is_ws(c) => {
614 ws = true;
615 self.pos += 1;
616 }
617 Some(b'/') if self.peek_at(1) == Some(b'*') => {
618 self.pos = match memchr::memmem::find(&self.b[self.pos + 2..], b"*/") {
619 Some(i) => self.pos + 2 + i + 2,
620 None => self.b.len(),
621 };
622 }
623 _ => return ws,
624 }
625 }
626 }
627
628 fn list(&mut self, relative: bool, nested: bool) -> Result<(Selector, u32), SelectorError> {
630 let mut list = Vec::new();
631 let mut depth = 0;
632 loop {
633 self.skip_ws();
634 let (c, d) = self.complex(relative)?;
635 list.push(c);
636 depth = depth.max(d);
637 self.skip_ws();
638 match self.peek() {
639 Some(b',') => self.pos += 1,
640 Some(b')') if nested => break,
641 None if !nested => break,
642 None => return Err(self.err("expected `)`")),
643 _ => return Err(self.unexpected()),
644 }
645 }
646 Ok((
647 Selector {
648 list: list.into_boxed_slice(),
649 },
650 depth,
651 ))
652 }
653
654 fn combinator(&mut self) -> Option<Comb> {
655 let c = match self.peek()? {
656 b'>' => Comb::Child,
657 b'+' => Comb::Next,
658 b'~' => Comb::Later,
659 _ => return None,
660 };
661 self.pos += 1;
662 Some(c)
663 }
664
665 fn complex(&mut self, relative: bool) -> Result<(Complex, u32), SelectorError> {
667 let mut parts: Vec<(Vec<Simple>, u32)> = Vec::new();
669 let mut combs: Vec<Comb> = Vec::new();
670 if relative {
671 let comb = self.combinator().unwrap_or(Comb::Descendant);
672 self.skip_ws();
673 parts.push((vec![Simple::Anchor], 0));
674 combs.push(comb);
675 }
676 parts.push(self.compound()?);
677 loop {
678 let ws = self.skip_ws();
679 let comb = match self.combinator() {
680 Some(c) => {
681 self.skip_ws();
682 c
683 }
684 None => match self.peek() {
685 None | Some(b',' | b')') => break,
686 Some(_) if ws => Comb::Descendant,
687 Some(_) => return Err(self.unexpected()),
688 },
689 };
690 combs.push(comb);
691 parts.push(self.compound()?);
692 }
693 let depth = parts.iter().map(|(_, d)| 1 + d).sum();
694 let n = parts.len();
695 let parts = parts
696 .into_iter()
697 .rev()
698 .enumerate()
699 .map(|(i, (mut simples, _))| {
700 simples.sort_by_key(Simple::cost);
701 let tag = simples.iter().find_map(|s| match s {
702 Simple::Type(t, _) if *t != Tag::Other => Some(*t),
703 _ => None,
704 });
705 Compound {
706 tag,
707 simples: simples.into_boxed_slice(),
708 comb: if i + 1 < n {
710 combs[n - 2 - i]
711 } else {
712 Comb::Descendant
713 },
714 }
715 })
716 .collect();
717 Ok((Complex { parts }, depth))
718 }
719
720 fn compound(&mut self) -> Result<(Vec<Simple>, u32), SelectorError> {
722 let mut simples = Vec::new();
723 let mut depth = 0;
724 let mut any = false;
725 match self.peek() {
726 Some(b'*') => {
727 self.pos += 1;
728 any = true;
729 }
730 _ if self.ident_start() => {
731 let name = self.ident()?.to_ascii_lowercase();
732 simples.push(Simple::Type(Tag::from_name(&name), name.into()));
733 any = true;
734 }
735 _ => {}
736 }
737 if self.peek() == Some(b'|') {
738 return Err(self.err("namespaces are not supported"));
739 }
740 loop {
741 match self.peek() {
742 Some(b'#') => {
743 self.pos += 1;
744 if !self.ident_start() {
745 return Err(self.err("expected an identifier after `#`"));
746 }
747 simples.push(Simple::Id(self.ident()?.into()));
748 }
749 Some(b'.') => {
750 self.pos += 1;
751 if !self.ident_start() {
752 return Err(self.err("expected an identifier after `.`"));
753 }
754 simples.push(Simple::Class(self.ident()?.into()));
755 }
756 Some(b'[') => {
757 self.pos += 1;
758 simples.push(self.attr()?);
759 }
760 Some(b':') => {
761 self.pos += 1;
762 let (s, d) = self.pseudo()?;
763 depth = depth.max(d);
764 simples.extend(s);
765 }
766 _ => break,
767 }
768 any = true;
769 }
770 if !any {
771 return Err(match self.peek() {
772 None => self.err("expected a selector"),
773 Some(_) => self.unexpected(),
774 });
775 }
776 Ok((simples, depth))
777 }
778
779 fn attr(&mut self) -> Result<Simple, SelectorError> {
780 self.skip_ws();
781 if matches!(self.peek(), Some(b'*' | b'|')) {
782 return Err(self.err("namespaces are not supported"));
783 }
784 if !self.ident_start() {
785 return Err(self.err("expected an attribute name"));
786 }
787 let name = self.ident()?.to_ascii_lowercase();
788 if self.peek() == Some(b'|') && self.peek_at(1) != Some(b'=') {
789 return Err(self.err("namespaces are not supported"));
790 }
791 self.skip_ws();
792 let op = match (self.peek(), self.peek_at(1)) {
793 (Some(b']'), _) => {
794 self.pos += 1;
795 return Ok(Simple::Attr(Box::new(AttrSel {
796 name: name.into(),
797 op: AttrOp::Exists,
798 value: "".into(),
799 ci: false,
800 })));
801 }
802 (Some(b'='), _) => AttrOp::Equals,
803 (Some(b'~'), Some(b'=')) => AttrOp::Includes,
804 (Some(b'|'), Some(b'=')) => AttrOp::Dash,
805 (Some(b'^'), Some(b'=')) => AttrOp::Prefix,
806 (Some(b'$'), Some(b'=')) => AttrOp::Suffix,
807 (Some(b'*'), Some(b'=')) => AttrOp::Substring,
808 (None, _) => return Err(self.err("unclosed attribute selector, expected `]`")),
809 _ => return Err(self.unexpected()),
810 };
811 self.pos += if op == AttrOp::Equals { 1 } else { 2 };
812 self.skip_ws();
813 let value = match self.peek() {
814 Some(q @ (b'"' | b'\'')) => self.string(q)?,
815 _ if self.ident_start() => self.ident()?,
816 None => return Err(self.err("expected an attribute value")),
817 _ => {
818 return Err(
819 self.err("expected an identifier or a quoted string as the attribute value")
820 );
821 }
822 };
823 self.skip_ws();
824 let mut ci = CASE_INSENSITIVE_ATTRS.contains(&name.as_str());
825 if self.ident_start() {
826 let flag_at = self.pos;
827 let flag = self.ident()?;
828 if flag.eq_ignore_ascii_case("i") {
829 ci = true;
830 } else if flag.eq_ignore_ascii_case("s") {
831 ci = false;
832 } else {
833 self.pos = flag_at;
834 return Err(self.err(format!("unknown attribute selector flag `{flag}`")));
835 }
836 self.skip_ws();
837 }
838 match self.peek() {
839 Some(b']') => self.pos += 1,
840 None => return Err(self.err("unclosed attribute selector, expected `]`")),
841 _ => return Err(self.unexpected()),
842 }
843 let op = match op {
844 AttrOp::Includes if value.is_empty() || value.bytes().any(is_ws) => AttrOp::Never,
845 AttrOp::Prefix | AttrOp::Suffix | AttrOp::Substring if value.is_empty() => {
846 AttrOp::Never
847 }
848 op => op,
849 };
850 Ok(Simple::Attr(Box::new(AttrSel {
851 name: name.into(),
852 op,
853 value: value.into(),
854 ci,
855 })))
856 }
857
858 fn pseudo(&mut self) -> Result<(Vec<Simple>, u32), SelectorError> {
860 if self.peek() == Some(b':') {
861 return Err(self.err("pseudo-elements are not supported"));
862 }
863 let start = self.pos;
864 if !self.ident_start() {
865 return Err(self.err("expected a pseudo-class name after `:`"));
866 }
867 let name = self.ident()?.to_ascii_lowercase();
868 if self.peek() == Some(b'(') {
869 self.pos += 1;
870 return self.pseudo_fn(&name, start);
871 }
872 let s = match name.as_str() {
873 "first-child" => vec![Nth::simple(0, 1, false, false)],
874 "last-child" => vec![Nth::simple(0, 1, true, false)],
875 "only-child" => vec![
876 Nth::simple(0, 1, false, false),
877 Nth::simple(0, 1, true, false),
878 ],
879 "first-of-type" => vec![Nth::simple(0, 1, false, true)],
880 "last-of-type" => vec![Nth::simple(0, 1, true, true)],
881 "only-of-type" => vec![
882 Nth::simple(0, 1, false, true),
883 Nth::simple(0, 1, true, true),
884 ],
885 "empty" => vec![Simple::Empty],
886 "root" => vec![Simple::Root],
887 "scope" => vec![Simple::Scope],
888 _ => {
889 self.pos = start;
890 return Err(self.err(format!("unsupported pseudo-class `:{name}`")));
891 }
892 };
893 Ok((s, 0))
894 }
895
896 fn pseudo_fn(&mut self, name: &str, start: usize) -> Result<(Vec<Simple>, u32), SelectorError> {
897 let nested = |p: &mut Self, relative: bool| -> Result<(Selector, u32), SelectorError> {
898 if p.nesting >= MAX_NESTING {
899 return Err(p.err(format!("selector nested deeper than {MAX_NESTING} levels")));
900 }
901 p.nesting += 1;
902 let r = p.list(relative, true);
903 p.nesting -= 1;
904 r
905 };
906 let (simple, depth) = match name {
907 "not" => {
908 let (s, d) = nested(self, false)?;
909 (Simple::Not(s), d)
910 }
911 "is" | "where" | "matches" | "any" => {
912 let (s, d) = nested(self, false)?;
913 (Simple::Is(s), d)
914 }
915 "has" => {
916 let (s, d) = nested(self, true)?;
917 (Simple::Has(s), d)
918 }
919 "nth-child" | "nth-last-child" | "nth-of-type" | "nth-last-of-type" => {
920 self.skip_ws();
921 let (a, b) = self.an_plus_b()?;
922 let ws = self.skip_ws();
923 let mut nth = Nth {
924 a,
925 b,
926 last: name.starts_with("nth-last"),
927 of_type: name.ends_with("of-type"),
928 of: None,
929 };
930 let mut depth = 0;
931 if !nth.of_type && ws && self.ident_start() {
932 let at = self.pos;
933 if self.ident()?.eq_ignore_ascii_case("of") && self.skip_ws() {
934 let (s, d) = nested(self, false)?;
935 nth.of = Some(s);
936 depth = d;
937 } else {
938 self.pos = at;
939 return Err(self.unexpected());
940 }
941 }
942 (Simple::Nth(Box::new(nth)), depth)
943 }
944 _ => {
945 self.pos = start;
946 return Err(self.err(format!("unsupported pseudo-class `:{name}()`")));
947 }
948 };
949 self.skip_ws();
950 match self.peek() {
951 Some(b')') => self.pos += 1,
952 None => return Err(self.err(format!("unclosed `:{name}(`, expected `)`"))),
953 _ => return Err(self.unexpected()),
954 }
955 Ok((vec![simple], depth))
956 }
957
958 fn an_plus_b(&mut self) -> Result<(i32, i32), SelectorError> {
960 let start = self.pos;
961 let bad = |p: &mut Self| {
962 p.pos = start;
963 Err(p.err("invalid An+B expression"))
964 };
965 if self.ident_start() {
966 let at = self.pos;
967 let word = self.ident()?;
968 if word.eq_ignore_ascii_case("odd") {
969 return Ok((2, 1));
970 }
971 if word.eq_ignore_ascii_case("even") {
972 return Ok((2, 0));
973 }
974 self.pos = at;
975 }
976 let sign = match self.peek() {
977 Some(b'+') => {
978 self.pos += 1;
979 1
980 }
981 Some(b'-') => {
982 self.pos += 1;
983 -1
984 }
985 _ => 1,
986 };
987 let digits = self.digits();
988 if matches!(self.peek(), Some(b'n' | b'N')) {
989 self.pos += 1;
990 let a = sign * digits.unwrap_or(1);
991 if self.peek().is_some_and(|c| is_name(c) && c != b'-') {
993 return bad(self);
994 }
995 let save = self.pos;
996 self.skip_ws();
997 let bsign = match self.peek() {
998 Some(b'+') => 1,
999 Some(b'-') => -1,
1000 _ => {
1001 self.pos = save;
1002 return Ok((a, 0));
1003 }
1004 };
1005 self.pos += 1;
1006 self.skip_ws();
1007 match self.digits() {
1008 Some(b) => Ok((a, bsign * b)),
1009 None => bad(self),
1010 }
1011 } else {
1012 match digits {
1013 Some(b) => Ok((0, sign * b)),
1014 None => bad(self),
1015 }
1016 }
1017 }
1018
1019 fn digits(&mut self) -> Option<i32> {
1020 let start = self.pos;
1021 let mut v: i32 = 0;
1022 while let Some(c @ b'0'..=b'9') = self.peek() {
1023 v = v.saturating_mul(10).saturating_add(i32::from(c - b'0'));
1024 self.pos += 1;
1025 }
1026 (self.pos > start).then_some(v)
1027 }
1028
1029 fn ident_start(&self) -> bool {
1031 let valid_escape = |i: usize| {
1032 self.peek_at(i) == Some(b'\\')
1033 && !matches!(self.peek_at(i + 1), Some(b'\n' | b'\r' | b'\x0c'))
1034 };
1035 match self.peek() {
1036 Some(b'-') => {
1037 matches!(self.peek_at(1), Some(c) if is_name_start(c) || c == b'-')
1038 || valid_escape(1)
1039 }
1040 Some(c) if is_name_start(c) => true,
1041 _ => valid_escape(0),
1042 }
1043 }
1044
1045 fn ident(&mut self) -> Result<String, SelectorError> {
1046 let mut out = String::new();
1047 loop {
1048 match self.peek() {
1049 Some(b'\\') if !matches!(self.peek_at(1), Some(b'\n' | b'\r' | b'\x0c')) => {
1050 self.pos += 1;
1051 out.push(self.escape());
1052 }
1053 Some(c) if is_name(c) => {
1054 let ch = self.s[self.pos..].chars().next().unwrap_or('\u{fffd}');
1055 out.push(ch);
1056 self.pos += ch.len_utf8();
1057 }
1058 _ => break,
1059 }
1060 }
1061 if out.is_empty() {
1062 return Err(self.err("expected an identifier"));
1063 }
1064 Ok(out)
1065 }
1066
1067 fn escape(&mut self) -> char {
1069 let start = self.pos;
1070 let mut v: u32 = 0;
1071 while self.pos - start < 6
1072 && let Some(c) = self.peek()
1073 && c.is_ascii_hexdigit()
1074 {
1075 v = v * 16 + (c as char).to_digit(16).unwrap_or(0);
1076 self.pos += 1;
1077 }
1078 if self.pos > start {
1079 if self.peek() == Some(b'\r') && self.peek_at(1) == Some(b'\n') {
1080 self.pos += 2;
1081 } else if self.peek().is_some_and(is_ws) {
1082 self.pos += 1;
1083 }
1084 return match char::from_u32(v) {
1085 Some(c) if v != 0 => c,
1086 _ => '\u{fffd}',
1087 };
1088 }
1089 match self.s[self.pos..].chars().next() {
1090 Some(c) => {
1091 self.pos += c.len_utf8();
1092 c
1093 }
1094 None => '\u{fffd}',
1095 }
1096 }
1097
1098 fn string(&mut self, q: u8) -> Result<String, SelectorError> {
1100 self.pos += 1;
1101 let mut out = String::new();
1102 loop {
1103 match self.peek() {
1104 None => return Ok(out),
1105 Some(c) if c == q => {
1106 self.pos += 1;
1107 return Ok(out);
1108 }
1109 Some(b'\n' | b'\r' | b'\x0c') => {
1110 return Err(self.err("unterminated string"));
1111 }
1112 Some(b'\\') => {
1113 self.pos += 1;
1114 match self.peek() {
1115 None => {}
1116 Some(b'\r') if self.peek_at(1) == Some(b'\n') => self.pos += 2,
1117 Some(b'\n' | b'\r' | b'\x0c') => self.pos += 1,
1118 Some(_) => out.push(self.escape()),
1119 }
1120 }
1121 Some(_) => {
1122 let ch = self.s[self.pos..].chars().next().unwrap_or('\u{fffd}');
1123 out.push(ch);
1124 self.pos += ch.len_utf8();
1125 }
1126 }
1127 }
1128 }
1129}
1130
1131#[cfg(test)]
1132mod tests {
1133 use super::*;
1134
1135 const DOC: &str = r##"<!doctype html><html lang="EN"><head><title>T</title></head><body>
1136 <div id="main" class="wrap big">
1137 <h1 class="title">Head</h1>
1138 <p class="lead intro" data-x="foo-bar">One</p>
1139 <p>Two <a href="https://a.example/x.pdf" rel="NoFollow">A</a></p>
1140 <p></p>
1141 <!-- c -->
1142 <ul><li>1</li><li class="odd">2</li><li>3</li><li>4</li><li>5</li></ul>
1143 <span lang="en-US">S</span><em>E</em><span>S2</span>
1144 <svg><a href="#s">svg-a</a></svg>
1145 <my-widget data-k="Ü">W</my-widget>
1146 </div>
1147 <table><tr><td>c1</td><td>c2</td></tr></table>
1148 </body></html>"##;
1149
1150 fn sel(doc: &Html<'_>, s: &str) -> Vec<String> {
1151 let sel = Selector::parse(s).unwrap_or_else(|e| panic!("{s}: {e}"));
1152 doc.select(&sel)
1153 .map(|n| {
1154 let e = n.as_element().unwrap();
1155 let t = n.text();
1156 if t.trim().is_empty() {
1157 e.name().to_owned()
1158 } else {
1159 t.trim().to_owned()
1160 }
1161 })
1162 .collect()
1163 }
1164
1165 #[test]
1166 fn simple_selectors() {
1167 let doc = Html::parse(DOC);
1168 assert_eq!(sel(&doc, "h1"), ["Head"]);
1169 assert_eq!(sel(&doc, "H1"), ["Head"]);
1170 assert_eq!(sel(&doc, "#main > .title"), ["Head"]);
1171 assert_eq!(sel(&doc, "p.lead.intro"), ["One"]);
1172 assert_eq!(sel(&doc, ".wrap.big > h1"), ["Head"]);
1173 assert!(sel(&doc, ".WRAP").is_empty());
1174 assert_eq!(sel(&doc, "my-widget"), ["W"]);
1175 assert_eq!(sel(&doc, "svg a"), ["svg-a"]);
1176 assert_eq!(sel(&doc, "a").len(), 2);
1177 assert_eq!(sel(&doc, "td:last-child"), ["c2"]);
1178 assert_eq!(
1179 sel(&doc, "*").len(),
1180 doc.root().descendants().filter(|n| n.is_element()).count()
1181 );
1182 assert_eq!(sel(&doc, "#m\\61 in > h1"), ["Head"]);
1183 assert_eq!(sel(&doc, "h1, li.odd, h1"), ["Head", "2"]);
1184 }
1185
1186 #[test]
1187 fn attribute_selectors() {
1188 let doc = Html::parse(DOC);
1189 assert_eq!(sel(&doc, "[data-x]"), ["One"]);
1190 assert_eq!(sel(&doc, "[data-x=foo-bar]"), ["One"]);
1191 assert_eq!(sel(&doc, "[data-x='foo-bar']"), ["One"]);
1192 assert_eq!(sel(&doc, "[data-x|=foo]"), ["One"]);
1193 assert!(sel(&doc, "[data-x|=fo]").is_empty());
1194 assert_eq!(sel(&doc, "[data-x^=foo]"), ["One"]);
1195 assert_eq!(sel(&doc, "[data-x$=\"bar\"]"), ["One"]);
1196 assert_eq!(sel(&doc, "[data-x*='o-b']"), ["One"]);
1197 assert!(sel(&doc, "[data-x*='O-B']").is_empty());
1198 assert_eq!(sel(&doc, "[data-x*='O-B' i]"), ["One"]);
1199 assert_eq!(sel(&doc, "[class~=intro]"), ["One"]);
1200 assert!(sel(&doc, "[class~='']").is_empty());
1201 assert!(sel(&doc, "[data-x^='']").is_empty());
1202 assert_eq!(sel(&doc, "a[href$='.PDF' i]"), ["A"]);
1203 assert_eq!(sel(&doc, "a[rel=nofollow]"), ["A"]);
1205 assert!(sel(&doc, "a[rel=nofollow s]").is_empty());
1206 assert_eq!(sel(&doc, "span[lang|=EN]"), ["S"]);
1207 assert_eq!(sel(&doc, "[data-k='Ü']"), ["W"]);
1208 }
1209
1210 #[test]
1211 fn combinators() {
1212 let doc = Html::parse(DOC);
1213 assert_eq!(sel(&doc, "body li"), ["1", "2", "3", "4", "5"]);
1214 assert!(sel(&doc, "body > li").is_empty());
1215 assert_eq!(sel(&doc, "h1 + p"), ["One"]);
1216 assert_eq!(sel(&doc, "h1 ~ p"), ["One", "Two A", "p"]);
1217 assert_eq!(sel(&doc, "li.odd ~ li"), ["3", "4", "5"]);
1218 assert_eq!(sel(&doc, "li.odd+li"), ["3"]);
1219 assert_eq!(sel(&doc, "span + em + span"), ["S2"]);
1220 assert_eq!(sel(&doc, "div p > a"), ["A"]);
1221 assert_eq!(sel(&doc, "html body div ul > li + li ~ li").len(), 3);
1222 assert_eq!(sel(&doc, "#main /* c */ h1"), ["Head"]);
1223 }
1224
1225 #[test]
1226 fn pseudo_classes() {
1227 let doc = Html::parse(DOC);
1228 assert_eq!(sel(&doc, "li:first-child"), ["1"]);
1229 assert_eq!(sel(&doc, "li:last-child"), ["5"]);
1230 assert_eq!(sel(&doc, "li:nth-child(2)"), ["2"]);
1231 assert_eq!(sel(&doc, "li:nth-child(odd)"), ["1", "3", "5"]);
1232 assert_eq!(sel(&doc, "li:nth-child(EVEN)"), ["2", "4"]);
1233 assert_eq!(sel(&doc, "li:nth-child(-n+2)"), ["1", "2"]);
1234 assert_eq!(sel(&doc, "li:nth-child(2n + 1)"), ["1", "3", "5"]);
1235 assert_eq!(sel(&doc, "li:nth-child(n+4)"), ["4", "5"]);
1236 assert_eq!(sel(&doc, "li:nth-last-child(2)"), ["4"]);
1237 assert_eq!(sel(&doc, "li:nth-child(odd of :not(.odd))"), ["1", "4"]);
1238 assert_eq!(sel(&doc, "#main > p:nth-of-type(2)"), ["Two A"]);
1239 assert_eq!(sel(&doc, "#main > span:last-of-type"), ["S2"]);
1240 assert_eq!(sel(&doc, "#main > :only-of-type").len(), 5);
1241 assert_eq!(sel(&doc, "title:only-child"), ["T"]);
1242 assert_eq!(sel(&doc, "#main > p:empty"), ["p"]);
1243 assert_eq!(sel(&doc, ":root").len(), 1);
1244 assert_eq!(sel(&doc, "p:not(.lead):not(:empty)"), ["Two A"]);
1245 assert_eq!(sel(&doc, "p:not(.lead, :empty)"), ["Two A"]);
1246 assert_eq!(sel(&doc, ":is(h1, li.odd)"), ["Head", "2"]);
1247 assert_eq!(sel(&doc, ":where(#main) > h1"), ["Head"]);
1248 assert_eq!(sel(&doc, "p:has(> a)"), ["Two A"]);
1249 assert_eq!(sel(&doc, "h1:has(+ p.lead)"), ["Head"]);
1250 assert_eq!(sel(&doc, "li:has(~ li.odd)"), ["1"]);
1251 assert_eq!(sel(&doc, "div:has(li.odd)").len(), 1);
1252 assert_eq!(sel(&doc, "body > :not(div, table)").len(), 0);
1253 }
1254
1255 #[test]
1256 fn scope() {
1257 let doc = Html::parse(DOC);
1258 let ul = doc.select(&"ul".parse().unwrap()).next().unwrap();
1259 let s: Selector = ":scope > li:first-child".parse().unwrap();
1260 assert_eq!(ul.select(&s).map(|n| n.text()).collect::<Vec<_>>(), ["1"]);
1261 let s: Selector = "ul".parse().unwrap();
1263 assert_eq!(ul.select(&s).count(), 0);
1264 let html = doc.select(&":scope".parse().unwrap()).collect::<Vec<_>>();
1265 assert_eq!(html.len(), 1);
1266 assert_eq!(html[0].tag(), Some(Tag::Html));
1267 }
1268
1269 #[test]
1270 fn errors() {
1271 for (s, msg) in [
1272 ("", "expected a selector"),
1273 ("div[", "attribute"),
1274 ("p:::x", "pseudo-elements"),
1275 ("p::before", "pseudo-elements"),
1276 ("a:hover", "unsupported pseudo-class `:hover`"),
1277 ("a:foo(1)", "unsupported pseudo-class `:foo()`"),
1278 ("a >", "expected a selector"),
1279 ("a,", "expected a selector"),
1280 (",a", "unexpected `,`"),
1281 ("a > > b", "unexpected `>`"),
1282 ("svg|a", "namespaces"),
1283 ("[x=1]", "attribute value"),
1284 ("[x=y z]", "unknown attribute selector flag"),
1285 ("li:nth-child(2nd)", "An+B"),
1286 ("li:nth-child(+ 2)", "An+B"),
1287 ("li:nth-child(2n+)", "An+B"),
1288 (":not(a", "expected `)`"),
1289 (":not()", "unexpected `)`"),
1290 ("a)", "unexpected `)`"),
1291 ("#1a", "identifier"),
1292 ("a.b*", "unexpected `*`"),
1293 ("[a='x\ny']", "unterminated string"),
1294 ] {
1295 let e = Selector::parse(s).expect_err(s);
1296 assert!(e.to_string().contains(msg), "{s:?}: {e}");
1297 }
1298 let deep = format!("{}a{}", ":not(".repeat(40), ")".repeat(40));
1299 assert!(
1300 Selector::parse(&deep)
1301 .unwrap_err()
1302 .to_string()
1303 .contains("nested deeper")
1304 );
1305 let long = vec!["a"; 300].join(" ");
1306 assert!(
1307 Selector::parse(&long)
1308 .unwrap_err()
1309 .to_string()
1310 .contains("too complex")
1311 );
1312 assert!(Selector::parse(&vec!["a"; 200].join(" ")).is_ok());
1313 }
1314
1315 #[test]
1316 fn descendant_backtracking_is_not_exponential() {
1317 let html = format!("{}{}", "<div>".repeat(400), "<span>x</span>");
1318 let doc = Html::parse(&html);
1319 let s: Selector = "div div div div div div div div div div p span"
1320 .parse()
1321 .unwrap();
1322 assert_eq!(doc.select(&s).count(), 0);
1323 let s: Selector = "div div div div div div div div div div span"
1324 .parse()
1325 .unwrap();
1326 assert_eq!(doc.select(&s).count(), 1);
1327 }
1328}