1use std::fmt;
23use std::str::FromStr;
24
25use crate::{Descendants, Html, NodeRef, Tag};
26
27pub(super) const 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)]
41pub(super) struct Complex {
42 pub(super) parts: Box<[Compound]>,
43}
44
45#[derive(Debug, Clone)]
46pub(super) struct Compound {
47 pub(super) tag: Option<Tag>,
49 pub(super) simples: Box<[Simple]>,
50 pub(super) comb: Comb,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub(super) enum Comb {
55 Descendant,
56 Child,
57 Next,
58 Later,
59}
60
61#[derive(Debug, Clone)]
62pub(super) enum 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 pub(super) 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)]
96pub(super) struct AttrSel {
97 pub(super) name: Box<str>,
98 pub(super) op: AttrOp,
99 pub(super) value: Box<str>,
100 pub(super) ci: bool,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub(super) enum AttrOp {
106 Exists,
107 Equals,
108 Includes,
109 Dash,
110 Prefix,
111 Suffix,
112 Substring,
113 Never,
115}
116
117#[derive(Debug, Clone)]
119pub(super) struct Nth {
120 pub(super) a: i32,
121 pub(super) b: i32,
122 pub(super) last: bool,
124 pub(super) of_type: bool,
126 pub(super) of: Option<Selector>,
128}
129
130impl Nth {
131 pub(super) 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 pub(super) 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
265mod matcher;
266mod parse;
267
268use matcher::{Ctx, Res, match_complex};
269use parse::Parser;
270
271pub(super) const CASE_INSENSITIVE_ATTRS: &[&str] = &[
273 "accept",
274 "accept-charset",
275 "align",
276 "alink",
277 "axis",
278 "bgcolor",
279 "charset",
280 "checked",
281 "clear",
282 "codetype",
283 "color",
284 "compact",
285 "declare",
286 "defer",
287 "dir",
288 "direction",
289 "disabled",
290 "enctype",
291 "face",
292 "frame",
293 "hreflang",
294 "http-equiv",
295 "lang",
296 "language",
297 "link",
298 "media",
299 "method",
300 "multiple",
301 "nohref",
302 "noresize",
303 "noshade",
304 "nowrap",
305 "readonly",
306 "rel",
307 "rev",
308 "rules",
309 "scope",
310 "scrolling",
311 "selected",
312 "shape",
313 "target",
314 "text",
315 "type",
316 "valign",
317 "valuetype",
318 "vlink",
319];
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 const DOC: &str = r##"<!doctype html><html lang="EN"><head><title>T</title></head><body>
326 <div id="main" class="wrap big">
327 <h1 class="title">Head</h1>
328 <p class="lead intro" data-x="foo-bar">One</p>
329 <p>Two <a href="https://a.example/x.pdf" rel="NoFollow">A</a></p>
330 <p></p>
331 <!-- c -->
332 <ul><li>1</li><li class="odd">2</li><li>3</li><li>4</li><li>5</li></ul>
333 <span lang="en-US">S</span><em>E</em><span>S2</span>
334 <svg><a href="#s">svg-a</a></svg>
335 <my-widget data-k="Ü">W</my-widget>
336 </div>
337 <table><tr><td>c1</td><td>c2</td></tr></table>
338 </body></html>"##;
339
340 fn sel(doc: &Html<'_>, s: &str) -> Vec<String> {
341 let sel = Selector::parse(s).unwrap_or_else(|e| panic!("{s}: {e}"));
342 doc.select(&sel)
343 .map(|n| {
344 let e = n.as_element().unwrap();
345 let t = n.text();
346 if t.trim().is_empty() {
347 e.name().to_owned()
348 } else {
349 t.trim().to_owned()
350 }
351 })
352 .collect()
353 }
354
355 #[test]
356 fn simple_selectors() {
357 let doc = Html::parse(DOC);
358 assert_eq!(sel(&doc, "h1"), ["Head"]);
359 assert_eq!(sel(&doc, "H1"), ["Head"]);
360 assert_eq!(sel(&doc, "#main > .title"), ["Head"]);
361 assert_eq!(sel(&doc, "p.lead.intro"), ["One"]);
362 assert_eq!(sel(&doc, ".wrap.big > h1"), ["Head"]);
363 assert!(sel(&doc, ".WRAP").is_empty());
364 assert_eq!(sel(&doc, "my-widget"), ["W"]);
365 assert_eq!(sel(&doc, "svg a"), ["svg-a"]);
366 assert_eq!(sel(&doc, "a").len(), 2);
367 assert_eq!(sel(&doc, "td:last-child"), ["c2"]);
368 assert_eq!(
369 sel(&doc, "*").len(),
370 doc.root().descendants().filter(|n| n.is_element()).count()
371 );
372 assert_eq!(sel(&doc, "#m\\61 in > h1"), ["Head"]);
373 assert_eq!(sel(&doc, "h1, li.odd, h1"), ["Head", "2"]);
374 }
375
376 #[test]
377 fn attribute_selectors() {
378 let doc = Html::parse(DOC);
379 assert_eq!(sel(&doc, "[data-x]"), ["One"]);
380 assert_eq!(sel(&doc, "[data-x=foo-bar]"), ["One"]);
381 assert_eq!(sel(&doc, "[data-x='foo-bar']"), ["One"]);
382 assert_eq!(sel(&doc, "[data-x|=foo]"), ["One"]);
383 assert!(sel(&doc, "[data-x|=fo]").is_empty());
384 assert_eq!(sel(&doc, "[data-x^=foo]"), ["One"]);
385 assert_eq!(sel(&doc, "[data-x$=\"bar\"]"), ["One"]);
386 assert_eq!(sel(&doc, "[data-x*='o-b']"), ["One"]);
387 assert!(sel(&doc, "[data-x*='O-B']").is_empty());
388 assert_eq!(sel(&doc, "[data-x*='O-B' i]"), ["One"]);
389 assert_eq!(sel(&doc, "[class~=intro]"), ["One"]);
390 assert!(sel(&doc, "[class~='']").is_empty());
391 assert!(sel(&doc, "[data-x^='']").is_empty());
392 assert_eq!(sel(&doc, "a[href$='.PDF' i]"), ["A"]);
393 assert_eq!(sel(&doc, "a[rel=nofollow]"), ["A"]);
395 assert!(sel(&doc, "a[rel=nofollow s]").is_empty());
396 assert_eq!(sel(&doc, "span[lang|=EN]"), ["S"]);
397 assert_eq!(sel(&doc, "[data-k='Ü']"), ["W"]);
398 }
399
400 #[test]
401 fn combinators() {
402 let doc = Html::parse(DOC);
403 assert_eq!(sel(&doc, "body li"), ["1", "2", "3", "4", "5"]);
404 assert!(sel(&doc, "body > li").is_empty());
405 assert_eq!(sel(&doc, "h1 + p"), ["One"]);
406 assert_eq!(sel(&doc, "h1 ~ p"), ["One", "Two A", "p"]);
407 assert_eq!(sel(&doc, "li.odd ~ li"), ["3", "4", "5"]);
408 assert_eq!(sel(&doc, "li.odd+li"), ["3"]);
409 assert_eq!(sel(&doc, "span + em + span"), ["S2"]);
410 assert_eq!(sel(&doc, "div p > a"), ["A"]);
411 assert_eq!(sel(&doc, "html body div ul > li + li ~ li").len(), 3);
412 assert_eq!(sel(&doc, "#main /* c */ h1"), ["Head"]);
413 }
414
415 #[test]
416 fn pseudo_classes() {
417 let doc = Html::parse(DOC);
418 assert_eq!(sel(&doc, "li:first-child"), ["1"]);
419 assert_eq!(sel(&doc, "li:last-child"), ["5"]);
420 assert_eq!(sel(&doc, "li:nth-child(2)"), ["2"]);
421 assert_eq!(sel(&doc, "li:nth-child(odd)"), ["1", "3", "5"]);
422 assert_eq!(sel(&doc, "li:nth-child(EVEN)"), ["2", "4"]);
423 assert_eq!(sel(&doc, "li:nth-child(-n+2)"), ["1", "2"]);
424 assert_eq!(sel(&doc, "li:nth-child(2n + 1)"), ["1", "3", "5"]);
425 assert_eq!(sel(&doc, "li:nth-child(n+4)"), ["4", "5"]);
426 assert_eq!(sel(&doc, "li:nth-last-child(2)"), ["4"]);
427 assert_eq!(sel(&doc, "li:nth-child(odd of :not(.odd))"), ["1", "4"]);
428 assert_eq!(sel(&doc, "#main > p:nth-of-type(2)"), ["Two A"]);
429 assert_eq!(sel(&doc, "#main > span:last-of-type"), ["S2"]);
430 assert_eq!(sel(&doc, "#main > :only-of-type").len(), 5);
431 assert_eq!(sel(&doc, "title:only-child"), ["T"]);
432 assert_eq!(sel(&doc, "#main > p:empty"), ["p"]);
433 assert_eq!(sel(&doc, ":root").len(), 1);
434 assert_eq!(sel(&doc, "p:not(.lead):not(:empty)"), ["Two A"]);
435 assert_eq!(sel(&doc, "p:not(.lead, :empty)"), ["Two A"]);
436 assert_eq!(sel(&doc, ":is(h1, li.odd)"), ["Head", "2"]);
437 assert_eq!(sel(&doc, ":where(#main) > h1"), ["Head"]);
438 assert_eq!(sel(&doc, "p:has(> a)"), ["Two A"]);
439 assert_eq!(sel(&doc, "h1:has(+ p.lead)"), ["Head"]);
440 assert_eq!(sel(&doc, "li:has(~ li.odd)"), ["1"]);
441 assert_eq!(sel(&doc, "div:has(li.odd)").len(), 1);
442 assert_eq!(sel(&doc, "body > :not(div, table)").len(), 0);
443 }
444
445 #[test]
446 fn scope() {
447 let doc = Html::parse(DOC);
448 let ul = doc.select(&"ul".parse().unwrap()).next().unwrap();
449 let s: Selector = ":scope > li:first-child".parse().unwrap();
450 assert_eq!(ul.select(&s).map(|n| n.text()).collect::<Vec<_>>(), ["1"]);
451 let s: Selector = "ul".parse().unwrap();
453 assert_eq!(ul.select(&s).count(), 0);
454 let html = doc.select(&":scope".parse().unwrap()).collect::<Vec<_>>();
455 assert_eq!(html.len(), 1);
456 assert_eq!(html[0].tag(), Some(Tag::Html));
457 }
458
459 #[test]
460 fn errors() {
461 for (s, msg) in [
462 ("", "expected a selector"),
463 ("div[", "attribute"),
464 ("p:::x", "pseudo-elements"),
465 ("p::before", "pseudo-elements"),
466 ("a:hover", "unsupported pseudo-class `:hover`"),
467 ("a:foo(1)", "unsupported pseudo-class `:foo()`"),
468 ("a >", "expected a selector"),
469 ("a,", "expected a selector"),
470 (",a", "unexpected `,`"),
471 ("a > > b", "unexpected `>`"),
472 ("svg|a", "namespaces"),
473 ("[x=1]", "attribute value"),
474 ("[x=y z]", "unknown attribute selector flag"),
475 ("li:nth-child(2nd)", "An+B"),
476 ("li:nth-child(+ 2)", "An+B"),
477 ("li:nth-child(2n+)", "An+B"),
478 (":not(a", "expected `)`"),
479 (":not()", "unexpected `)`"),
480 ("a)", "unexpected `)`"),
481 ("#1a", "identifier"),
482 ("a.b*", "unexpected `*`"),
483 ("[a='x\ny']", "unterminated string"),
484 ] {
485 let e = Selector::parse(s).expect_err(s);
486 assert!(e.to_string().contains(msg), "{s:?}: {e}");
487 }
488 let deep = format!("{}a{}", ":not(".repeat(40), ")".repeat(40));
489 assert!(
490 Selector::parse(&deep)
491 .unwrap_err()
492 .to_string()
493 .contains("nested deeper")
494 );
495 let long = vec!["a"; 300].join(" ");
496 assert!(
497 Selector::parse(&long)
498 .unwrap_err()
499 .to_string()
500 .contains("too complex")
501 );
502 assert!(Selector::parse(&vec!["a"; 200].join(" ")).is_ok());
503 }
504
505 #[test]
506 fn descendant_backtracking_is_not_exponential() {
507 let html = format!("{}{}", "<div>".repeat(400), "<span>x</span>");
508 let doc = Html::parse(&html);
509 let s: Selector = "div div div div div div div div div div p span"
510 .parse()
511 .unwrap();
512 assert_eq!(doc.select(&s).count(), 0);
513 let s: Selector = "div div div div div div div div div div span"
514 .parse()
515 .unwrap();
516 assert_eq!(doc.select(&s).count(), 1);
517 }
518}