rama_http/protocols/html/selector/mod.rs
1//! Native CSS selector parsing and matching.
2//!
3//! This module provides a small, dependency-free CSS selector engine for
4//! the subset of selectors that can be evaluated while *streaming* HTML
5//! (no sibling lookahead, no full-document state). It is the foundation
6//! for rama's streaming HTML parser/rewriter, but is also usable on its
7//! own to match selectors against any tree that implements
8//! [`SelectorSubject`] — including the in-memory [`Dom`] provided here.
9//!
10//! # Supported selectors
11//!
12//! | Pattern | Meaning |
13//! | --- | --- |
14//! | `*` | any element |
15//! | `E` | an element of type `E` (ASCII case-insensitive) |
16//! | `E.cls` | an `E` with class `cls` |
17//! | `E#id` | an `E` with id `id` |
18//! | `E[a]` | an `E` with an `a` attribute |
19//! | `E[a=v]` | value exactly `v` |
20//! | `E[a~=v]` | whitespace-separated list containing `v` |
21//! | `E[a^=v]` `E[a$=v]` `E[a*=v]` | prefix / suffix / substring |
22//! | `E[a\|=v]` | `v` or `v-` prefix |
23//! | `E[a=v i]` / `E[a=v s]` | case-insensitive / case-sensitive value |
24//! | `E:nth-child(An+B)` `E:first-child` | structural position |
25//! | `E:nth-of-type(An+B)` `E:first-of-type` | structural position by type |
26//! | `E:not(s)` | negation of a combinator-free compound `s` |
27//! | `E F` / `E > F` | descendant / child combinator |
28//! | `a, b, c` | selector list (matches if any matches) |
29//!
30//! Sibling combinators (`+`, `~`), `:has()`, `:is()`, `:where()`,
31//! namespaces, pseudo-elements, and the non-streamable structural pseudos
32//! (`:last-child`, `:only-child`, `:last-of-type`, `:only-of-type`,
33//! `:nth-last-*`) are intentionally rejected with a [`SelectorError`].
34
35pub(crate) mod ast;
36mod builder;
37mod display;
38mod dom;
39mod matcher;
40mod parser;
41
42#[cfg(test)]
43mod tests;
44
45pub use self::ast::{Compound, Selector};
46pub use self::dom::{Dom, Element, NodeId};
47pub use self::matcher::SelectorSubject;
48
49use std::fmt;
50
51/// Error returned when parsing a CSS selector string fails, or when it
52/// uses a construct outside the streaming-safe [supported subset](self).
53#[derive(Debug, Clone, PartialEq, Eq)]
54#[non_exhaustive]
55pub enum SelectorError {
56 /// The selector string was empty.
57 EmptySelector,
58 /// An unexpected character/token was encountered.
59 UnexpectedToken,
60 /// The selector ended while more input was expected.
61 UnexpectedEnd,
62 /// A combinator was not followed by a compound selector (e.g. `div >`).
63 DanglingCombinator,
64 /// An attribute selector was missing its attribute name (e.g. `[=x]`).
65 MissingAttributeName,
66 /// An unexpected token inside an attribute selector.
67 UnexpectedTokenInAttribute,
68 /// A sibling combinator (`+` or `~`) was used; not supported while
69 /// streaming.
70 UnsupportedCombinator(char),
71 /// An unsupported pseudo-class or pseudo-element was used.
72 UnsupportedPseudoClass,
73 /// A selector used an explicit namespace (e.g. `svg|rect`).
74 NamespacedSelector,
75 /// A `:not()` had no argument.
76 EmptyNegation,
77 /// An `An+B` micro-syntax value was malformed.
78 InvalidNth,
79}
80
81impl fmt::Display for SelectorError {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 Self::EmptySelector => f.write_str("empty selector"),
85 Self::UnexpectedToken => f.write_str("unexpected token in selector"),
86 Self::UnexpectedEnd => f.write_str("unexpected end of selector"),
87 Self::DanglingCombinator => f.write_str("dangling combinator in selector"),
88 Self::MissingAttributeName => {
89 f.write_str("missing attribute name in attribute selector")
90 }
91 Self::UnexpectedTokenInAttribute => {
92 f.write_str("unexpected token in attribute selector")
93 }
94 Self::UnsupportedCombinator(c) => {
95 write!(f, "unsupported combinator `{c}` in selector")
96 }
97 Self::UnsupportedPseudoClass => {
98 f.write_str("unsupported pseudo-class or pseudo-element in selector")
99 }
100 Self::NamespacedSelector => {
101 f.write_str("selectors with explicit namespaces are not supported")
102 }
103 Self::EmptyNegation => f.write_str("empty `:not()` in selector"),
104 Self::InvalidNth => f.write_str("invalid `An+B` value in selector"),
105 }
106 }
107}
108
109impl std::error::Error for SelectorError {}