1use std::collections::{BTreeSet, HashMap};
29
30use crate::dom::Dom;
31use crate::node::NodeData;
32use crate::node_id::NodeId;
33
34pub(crate) type Bucket = BTreeSet<NodeId>;
35
36#[derive(Debug, Default, Clone)]
37pub(crate) struct Indexes {
38 pub(crate) by_id: HashMap<String, Bucket>,
39 pub(crate) by_tag: HashMap<String, Bucket>,
40 pub(crate) by_class: HashMap<String, Bucket>,
41}
42
43impl Indexes {
44 fn push_unique(bucket: &mut Bucket, id: NodeId) {
45 bucket.insert(id);
46 }
47
48 fn remove_from(map: &mut HashMap<String, Bucket>, key: &str, id: NodeId) {
49 if let Some(bucket) = map.get_mut(key) {
50 bucket.remove(&id);
51 if bucket.is_empty() {
52 map.remove(key);
53 }
54 }
55 }
56
57 pub(crate) fn register_id(&mut self, id: NodeId, id_value: &str) {
58 if id_value.is_empty() {
59 return;
60 }
61 Self::push_unique(self.by_id.entry(id_value.to_string()).or_default(), id);
62 }
63
64 pub(crate) fn unregister_id(&mut self, id: NodeId, id_value: &str) {
65 if id_value.is_empty() {
66 return;
67 }
68 Self::remove_from(&mut self.by_id, id_value, id);
69 }
70
71 pub(crate) fn register_tag(&mut self, id: NodeId, tag: &str) {
72 Self::push_unique(self.by_tag.entry(tag.to_string()).or_default(), id);
73 }
74
75 pub(crate) fn unregister_tag(&mut self, id: NodeId, tag: &str) {
76 Self::remove_from(&mut self.by_tag, tag, id);
77 }
78
79 pub(crate) fn register_class(&mut self, id: NodeId, class: &str) {
80 Self::push_unique(self.by_class.entry(class.to_string()).or_default(), id);
81 }
82
83 pub(crate) fn unregister_class(&mut self, id: NodeId, class: &str) {
84 Self::remove_from(&mut self.by_class, class, id);
85 }
86}
87
88impl<Ext> Dom<Ext> {
91 pub(crate) fn hook_register(&mut self, id: NodeId) {
94 let Some(node) = self.get_node(id) else {
95 return;
96 };
97 let (tag, id_attr, classes) = match &node.data {
98 NodeData::Element {
99 tag,
100 attrs,
101 classes,
102 ..
103 } => {
104 let tag = tag.clone();
105 let id_attr = attrs.get("id").cloned();
106 let classes: Vec<String> = classes.iter().cloned().collect();
107 (tag, id_attr, classes)
108 }
109 _ => return,
110 };
111 self.indexes.register_tag(id, &tag);
112 if let Some(v) = id_attr {
113 self.indexes.register_id(id, &v);
114 }
115 for c in classes {
116 self.indexes.register_class(id, &c);
117 }
118 }
119
120 pub(crate) fn hook_unregister(&mut self, id: NodeId) {
123 let Some(node) = self.get_node(id) else {
124 return;
125 };
126 let (tag, id_attr, classes) = match &node.data {
127 NodeData::Element {
128 tag,
129 attrs,
130 classes,
131 ..
132 } => {
133 let tag = tag.clone();
134 let id_attr = attrs.get("id").cloned();
135 let classes: Vec<String> = classes.iter().cloned().collect();
136 (tag, id_attr, classes)
137 }
138 _ => return,
139 };
140 self.indexes.unregister_tag(id, &tag);
141 if let Some(v) = id_attr {
142 self.indexes.unregister_id(id, &v);
143 }
144 for c in classes {
145 self.indexes.unregister_class(id, &c);
146 }
147 }
148
149 pub fn get_element_by_id(&self, id_value: &str) -> Option<NodeId> {
163 let bucket = self.indexes.by_id.get(id_value)?;
164 if bucket.len() == 1 {
165 return bucket.iter().next().copied();
166 }
167 use crate::position::DocumentPosition;
171 let root = self.root();
172 let mut best: Option<NodeId> = None;
173 for &candidate in bucket {
174 let connected = self.root_of(candidate) == Some(root);
175 if !connected {
176 continue;
177 }
178 best = Some(match best {
179 None => candidate,
180 Some(b)
181 if self
182 .compare_document_position(b, candidate)
183 .contains(DocumentPosition::PRECEDING) =>
184 {
185 candidate
186 }
187 Some(b) => b,
188 });
189 }
190 best.or_else(|| bucket.iter().next().copied())
191 }
192
193 pub fn get_elements_by_tag_name_all(&self, tag: &str) -> Vec<NodeId> {
197 if tag == "*" {
198 let mut out: Vec<NodeId> = self
200 .indexes
201 .by_tag
202 .values()
203 .flat_map(|b| b.iter().copied())
204 .collect();
205 out.sort_unstable();
206 out
207 } else {
208 self.indexes
209 .by_tag
210 .get(tag)
211 .map(|b| b.iter().copied().collect())
212 .unwrap_or_default()
213 }
214 }
215
216 pub fn get_elements_by_class_name_all(&self, names: &str) -> Vec<NodeId> {
220 let wanted: Vec<&str> = names.split_ascii_whitespace().collect();
221 if wanted.is_empty() {
222 return self.get_elements_by_tag_name_all("*");
223 }
224 let mut buckets: Vec<&Bucket> = wanted
226 .iter()
227 .filter_map(|w| self.indexes.by_class.get(*w))
228 .collect();
229 if buckets.len() != wanted.len() {
230 return Vec::new(); }
232 buckets.sort_by_key(|b| b.len());
233 let smallest = buckets[0];
234 smallest
236 .iter()
237 .copied()
238 .filter(|id| buckets[1..].iter().all(|b| b.contains(id)))
239 .collect()
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use crate::Dom;
246
247 #[test]
250 fn duplicate_ids_resolve_in_document_order() {
251 let mut dom: Dom = Dom::new();
252 let root = dom.root();
253 let later = dom.create_element("p");
254 dom.set_attribute(later, "id", "dup").unwrap();
255 let earlier = dom.create_element("p");
256 dom.set_attribute(earlier, "id", "dup").unwrap();
257 dom.append_child(root, later).unwrap();
259 dom.insert_before(root, earlier, Some(later)).unwrap();
260 assert_eq!(dom.get_element_by_id("dup"), Some(earlier));
261 dom.remove_child_dropping(root, earlier).unwrap();
263 assert_eq!(dom.get_element_by_id("dup"), Some(later));
264 }
265
266 #[test]
271 fn tag_index_stays_exact_across_many_nodes() {
272 let mut dom: Dom = Dom::new();
273 let root = dom.root();
274 let ids: Vec<_> = (0..20_000)
275 .map(|_| {
276 let d = dom.create_element("div");
277 dom.append_child(root, d).unwrap();
278 d
279 })
280 .collect();
281 assert_eq!(dom.get_elements_by_tag_name_all("div").len(), 20_000);
282 for id in ids {
283 dom.remove_child_dropping(root, id).unwrap();
284 }
285 assert!(dom.get_elements_by_tag_name_all("div").is_empty());
286 assert!(
287 !dom.indexes.by_tag.contains_key("div"),
288 "empty bucket is removed"
289 );
290 assert!(dom.validate().is_empty());
291 }
292
293 #[test]
294 fn id_index_populated_on_set_attribute() {
295 let mut dom: Dom = Dom::new();
296 let el = dom.create_element("div");
297 dom.set_attribute(el, "id", "main").unwrap();
298 assert_eq!(dom.get_element_by_id("main"), Some(el));
299 }
300
301 #[test]
302 fn id_index_unregisters_on_removal() {
303 let mut dom: Dom = Dom::new();
304 let el = dom.create_element("div");
305 dom.set_attribute(el, "id", "main").unwrap();
306 dom.remove_attribute(el, "id").unwrap();
307 assert_eq!(dom.get_element_by_id("main"), None);
308 }
309
310 #[test]
311 fn id_index_updates_on_reassignment() {
312 let mut dom: Dom = Dom::new();
313 let el = dom.create_element("div");
314 dom.set_attribute(el, "id", "old").unwrap();
315 dom.set_attribute(el, "id", "new").unwrap();
316 assert_eq!(dom.get_element_by_id("old"), None);
317 assert_eq!(dom.get_element_by_id("new"), Some(el));
318 }
319
320 #[test]
321 fn id_index_survives_node_drop() {
322 let mut dom: Dom = Dom::new();
323 let el = dom.create_element("div");
324 dom.set_attribute(el, "id", "main").unwrap();
325 let root = dom.root();
326 dom.append_child(root, el).unwrap();
327 dom.drop_subtree(el).unwrap();
328 assert_eq!(dom.get_element_by_id("main"), None);
329 }
330
331 #[test]
332 fn tag_index_finds_elements() {
333 let mut dom: Dom = Dom::new();
334 let a = dom.create_element("div");
335 let b = dom.create_element("div");
336 let c = dom.create_element("span");
337 let divs = dom.get_elements_by_tag_name_all("div");
338 assert!(divs.contains(&a));
339 assert!(divs.contains(&b));
340 assert!(!divs.contains(&c));
341 }
342
343 #[test]
344 fn tag_index_wildcard_returns_all() {
345 let mut dom: Dom = Dom::new();
346 let _ = dom.create_element("a");
347 let _ = dom.create_element("b");
348 let all = dom.get_elements_by_tag_name_all("*");
350 assert_eq!(all.len(), 2);
351 }
352
353 #[test]
354 fn tag_index_clears_on_free() {
355 let mut dom: Dom = Dom::new();
356 let a = dom.create_element("a");
357 assert_eq!(dom.get_elements_by_tag_name_all("a"), vec![a]);
358 let root = dom.root();
359 dom.append_child(root, a).unwrap();
360 dom.drop_subtree(a).unwrap();
361 assert!(dom.get_elements_by_tag_name_all("a").is_empty());
362 }
363
364 #[test]
365 fn class_index_basic() {
366 let mut dom: Dom = Dom::new();
367 let el = dom.create_element("div");
368 dom.add_class(el, "foo").unwrap();
369 assert_eq!(dom.get_elements_by_class_name_all("foo"), vec![el]);
370 }
371
372 #[test]
373 fn class_index_intersection() {
374 let mut dom: Dom = Dom::new();
375 let a = dom.create_element("div");
376 dom.add_class(a, "x").unwrap();
377 dom.add_class(a, "y").unwrap();
378 let b = dom.create_element("div");
379 dom.add_class(b, "x").unwrap(); let c = dom.create_element("div");
381 dom.add_class(c, "y").unwrap(); assert_eq!(dom.get_elements_by_class_name_all("x y"), vec![a]);
384 let xs = dom.get_elements_by_class_name_all("x");
385 assert!(xs.contains(&a) && xs.contains(&b));
386 }
387
388 #[test]
389 fn class_index_handles_toggle_and_replace() {
390 let mut dom: Dom = Dom::new();
391 let el = dom.create_element("div");
392 dom.add_class(el, "old").unwrap();
393 assert_eq!(dom.get_elements_by_class_name_all("old"), vec![el]);
394
395 dom.replace_class(el, "old", "new").unwrap();
396 assert!(dom.get_elements_by_class_name_all("old").is_empty());
397 assert_eq!(dom.get_elements_by_class_name_all("new"), vec![el]);
398
399 dom.toggle_class(el, "new").unwrap(); assert!(dom.get_elements_by_class_name_all("new").is_empty());
401 }
402
403 #[test]
404 fn id_attribute_via_set_id_sugar_indexed() {
405 let mut dom: Dom = Dom::new();
406 let el = dom.create_element("div");
407 dom.set_id(el, "hero").unwrap();
408 assert_eq!(dom.get_element_by_id("hero"), Some(el));
409 }
410
411 #[test]
412 fn freed_slot_reuse_does_not_leak_old_index_entries() {
413 let mut dom: Dom = Dom::new();
414 let a = dom.create_element("div");
415 dom.set_attribute(a, "id", "x").unwrap();
416 dom.add_class(a, "c").unwrap();
417 dom.free(a); let b = dom.create_element("span");
421 assert_eq!(dom.get_element_by_id("x"), None);
422 assert!(dom.get_elements_by_class_name_all("c").is_empty());
423 assert_eq!(dom.get_elements_by_tag_name_all("span"), vec![b]);
424 assert!(dom.get_elements_by_tag_name_all("div").is_empty());
425 }
426
427 #[test]
428 fn duplicate_ids_first_wins() {
429 let mut dom: Dom = Dom::new();
430 let a = dom.create_element("div");
431 dom.set_attribute(a, "id", "dup").unwrap();
432 let b = dom.create_element("span");
433 dom.set_attribute(b, "id", "dup").unwrap();
434 assert_eq!(dom.get_element_by_id("dup"), Some(a));
435 dom.remove_attribute(a, "id").unwrap();
437 assert_eq!(dom.get_element_by_id("dup"), Some(b));
438 }
439}