1use crate::Node;
2
3#[derive(Debug, Clone)]
5pub enum Selector<'a, const MAX_SELECTOR_NODES: usize = 0> {
6 Tag(&'a [u8]),
8 Id(&'a [u8]),
10 Class(&'a [u8]),
12 All,
14 #[cfg(feature = "std")]
16 And(
17 Box<Selector<'a, MAX_SELECTOR_NODES>>,
18 Box<Selector<'a, MAX_SELECTOR_NODES>>,
19 ),
20 #[cfg(feature = "std")]
22 Or(
23 Box<Selector<'a, MAX_SELECTOR_NODES>>,
24 Box<Selector<'a, MAX_SELECTOR_NODES>>,
25 ),
26 #[cfg(feature = "std")]
28 Descendant(
29 Box<Selector<'a, MAX_SELECTOR_NODES>>,
30 Box<Selector<'a, MAX_SELECTOR_NODES>>,
31 ),
32 #[cfg(feature = "std")]
34 Parent(
35 Box<Selector<'a, MAX_SELECTOR_NODES>>,
36 Box<Selector<'a, MAX_SELECTOR_NODES>>,
37 ),
38 #[cfg(feature = "std")]
40 Adjacent(Box<Self>, Box<Self>),
41 #[cfg(feature = "std")]
43 Sibling(Box<Self>, Box<Self>),
44 #[cfg(feature = "std")]
46 Not(Box<Self>),
47 #[cfg(feature = "std")]
50 Has(Box<Self>),
51 #[cfg(feature = "std")]
53 NthChild(i32, i32),
54 Attribute(&'a [u8]),
56 AttributeValue(&'a [u8], &'a [u8]),
58 AttributeValueWhitespacedContains(&'a [u8], &'a [u8]),
60 AttributeValueStartsWith(&'a [u8], &'a [u8]),
62 AttributeValueEndsWith(&'a [u8], &'a [u8]),
64 AttributeValueSubstring(&'a [u8], &'a [u8]),
66}
67
68impl<'a, const MAX_SELECTOR_NODES: usize> Selector<'a, MAX_SELECTOR_NODES> {
69 pub fn matches<'b>(&self, node: &Node<'b>) -> bool {
71 match self {
72 Self::Tag(tag) => node
73 .as_tag()
74 .is_some_and(|t| t._name.as_bytes().eq_ignore_ascii_case(tag)),
75 Self::Id(id) => node
76 .as_tag()
77 .is_some_and(|t| t._attributes.id == Some((*id).into())),
78 Self::Class(class) => node
79 .as_tag()
80 .is_some_and(|t| t._attributes.is_class_member(*class)),
81 #[cfg(feature = "std")]
82 Self::And(a, b) => a.matches(node) && b.matches(node),
83 #[cfg(feature = "std")]
84 Self::Or(a, b) => a.matches(node) || b.matches(node),
85 #[cfg(feature = "std")]
86 Self::Not(a) => node.as_tag().is_some() && !a.matches(node),
87 Self::All => node.as_tag().is_some(),
88 Self::Attribute(attribute) => node
89 .as_tag()
90 .is_some_and(|t| t._attributes.get(*attribute).is_some()),
91 Self::AttributeValue(attribute, value) => {
92 check_attribute(node, attribute, value, |attr, value| attr == value)
93 }
94 Self::AttributeValueEndsWith(attribute, value) => {
95 check_attribute(node, attribute, value, |attr, value| {
96 !value.is_empty() && attr.ends_with(value)
97 })
98 }
99 Self::AttributeValueStartsWith(attribute, value) => {
100 check_attribute(node, attribute, value, |attr, value| {
101 !value.is_empty() && attr.starts_with(value)
102 })
103 }
104 Self::AttributeValueSubstring(attribute, value) => {
105 check_attribute(node, attribute, value, |attr, value| {
106 !value.is_empty() && attr.contains(value)
107 })
108 }
109 Self::AttributeValueWhitespacedContains(attribute, value) => {
110 check_attribute(node, attribute, value, |attr, value| {
111 attr.split_whitespace().any(|x| x == value)
112 })
113 }
114 #[cfg(feature = "std")]
115 _ => false,
116 }
117 }
118}
119
120fn check_attribute<F>(node: &Node, attribute: &[u8], value: &[u8], callback: F) -> bool
121where
122 F: Fn(&str, &str) -> bool,
123{
124 let Ok(value) = core::str::from_utf8(value) else {
125 return false;
126 };
127 node.as_tag().is_some_and(|t| {
128 t._attributes
129 .get(attribute)
130 .flatten()
131 .and_then(|attr| attr.try_as_utf8_str())
132 .is_some_and(|attr| callback(attr, value))
133 })
134}
135
136#[cfg(feature = "std")]
137#[derive(Clone)]
138pub(crate) struct Context {
139 parent: Vec<Option<usize>>,
140 previous: Vec<Option<usize>>,
141 position: Vec<usize>,
142}
143
144#[cfg(feature = "std")]
145impl Context {
146 pub(crate) fn new(nodes: &[Node<'_>]) -> Self {
147 let mut result = Self {
148 parent: vec![None; nodes.len()],
149 previous: vec![None; nodes.len()],
150 position: vec![0; nodes.len()],
151 };
152 for (id, node) in nodes.iter().enumerate() {
153 if let Some(tag) = node.as_tag() {
154 let mut previous = None;
155 let mut position = 0;
156 for child in tag.children().top().iter() {
157 let child = child.get_inner() as usize;
158 if let Some(node) = nodes.get(child) {
159 result.parent[child] = Some(id);
160 if node.as_tag().is_some() {
161 position += 1;
162 result.previous[child] = previous;
163 result.position[child] = position;
164 previous = Some(child);
165 }
166 }
167 }
168 }
169 }
170 let mut previous = None;
171 let mut position = 0;
172 for (id, node) in nodes.iter().enumerate() {
173 if result.parent[id].is_none() && node.as_tag().is_some() {
174 position += 1;
175 result.previous[id] = previous;
176 result.position[id] = position;
177 previous = Some(id);
178 }
179 }
180 result
181 }
182}
183
184#[cfg(feature = "std")]
185impl<const N: usize> Selector<'_, N> {
186 pub(crate) fn needs_context(&self) -> bool {
187 match self {
188 Self::And(a, b) | Self::Or(a, b) => a.needs_context() || b.needs_context(),
189 Self::Not(a) => a.needs_context(),
190 Self::Parent(..)
191 | Self::Descendant(..)
192 | Self::Adjacent(..)
193 | Self::Sibling(..)
194 | Self::NthChild(..)
195 | Self::Has(..) => true,
196 _ => false,
197 }
198 }
199
200 pub(crate) fn matches_in(&self, id: usize, nodes: &[Node<'_>], ctx: &Context) -> bool {
201 self.matches_scoped(id, nodes, ctx, None)
202 }
203
204 fn matches_scoped(
205 &self,
206 id: usize,
207 nodes: &[Node<'_>],
208 ctx: &Context,
209 scope: Option<usize>,
210 ) -> bool {
211 let Some(node) = nodes.get(id) else {
212 return false;
213 };
214 if node.as_tag().is_none() {
215 return false;
216 }
217 match self {
218 Self::And(a, b) => {
219 a.matches_scoped(id, nodes, ctx, scope) && b.matches_scoped(id, nodes, ctx, scope)
220 }
221 Self::Or(a, b) => {
222 a.matches_scoped(id, nodes, ctx, scope) || b.matches_scoped(id, nodes, ctx, scope)
223 }
224 Self::Not(a) => !a.matches_scoped(id, nodes, ctx, scope),
225 Self::Parent(a, b) => {
226 b.matches_scoped(id, nodes, ctx, scope)
227 && ctx.parent[id]
228 .filter(|p| Some(*p) != scope)
229 .is_some_and(|p| a.matches_scoped(p, nodes, ctx, scope))
230 }
231 Self::Adjacent(a, b) => {
232 b.matches_scoped(id, nodes, ctx, scope)
233 && ctx.previous[id].is_some_and(|p| a.matches_scoped(p, nodes, ctx, scope))
234 }
235 Self::Descendant(a, b) | Self::Sibling(a, b) => {
236 if !b.matches_scoped(id, nodes, ctx, scope) {
237 return false;
238 }
239 let links = if matches!(self, Self::Descendant(..)) {
240 &ctx.parent
241 } else {
242 &ctx.previous
243 };
244 let mut cursor = links[id]
245 .filter(|next| !matches!(self, Self::Descendant(..)) || Some(*next) != scope);
246 for _ in 0..nodes.len() {
248 let Some(p) = cursor else {
249 break;
250 };
251 if a.matches_scoped(p, nodes, ctx, scope) {
252 return true;
253 }
254 cursor = links[p].filter(|next| {
255 !matches!(self, Self::Descendant(..)) || Some(*next) != scope
256 });
257 }
258 false
259 }
260 Self::NthChild(a, b) => {
261 let delta = ctx.position[id] as i64 - i64::from(*b);
262 let a = i64::from(*a);
263 if a == 0 {
264 delta == 0
265 } else {
266 delta % a == 0 && delta / a >= 0
267 }
268 }
269 Self::Has(a) => {
270 nodes.iter().enumerate().any(|(candidate, _)| {
273 let mut cursor = ctx.parent[candidate];
274 for _ in 0..nodes.len() {
275 let Some(p) = cursor else {
276 break;
277 };
278 if p == id {
279 return a.matches_scoped(candidate, nodes, ctx, Some(id));
280 }
281 cursor = ctx.parent[p];
282 }
283 false
284 })
285 }
286 _ => self.matches(node),
287 }
288 }
289}