brik/select/
element_impl.rs1use super::{AttrValue, BrikSelectors, LocalNameSelector, PseudoClass, PseudoElement};
2use crate::attributes::ExpandedName;
3use crate::iter::NodeIterator;
4use crate::node_data_ref::NodeDataRef;
5use crate::tree::{ElementData, Node, NodeData, NodeRef};
6use html5ever::{local_name, ns, LocalName, Namespace};
7use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
8use selectors::{matching, OpaqueElement};
9
10pub(super) static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C'];
14
15impl selectors::Element for NodeDataRef<ElementData> {
22 type Impl = BrikSelectors;
23
24 #[inline]
25 fn opaque(&self) -> OpaqueElement {
26 let node: &Node = self.as_node();
27 OpaqueElement::new(node)
28 }
29
30 #[inline]
31 fn is_html_slot_element(&self) -> bool {
32 false
33 }
34 #[inline]
35 fn parent_node_is_shadow_root(&self) -> bool {
36 false
37 }
38 #[inline]
39 fn containing_shadow_host(&self) -> Option<Self> {
40 None
41 }
42
43 #[inline]
44 fn parent_element(&self) -> Option<Self> {
45 self.as_node().parent().and_then(NodeRef::into_element_ref)
46 }
47 #[inline]
48 fn prev_sibling_element(&self) -> Option<Self> {
49 self.as_node().preceding_siblings().elements().next()
50 }
51 #[inline]
52 fn next_sibling_element(&self) -> Option<Self> {
53 self.as_node().following_siblings().elements().next()
54 }
55 #[inline]
56 fn first_element_child(&self) -> Option<Self> {
57 self.as_node().children().elements().next()
58 }
59 #[inline]
60 fn is_empty(&self) -> bool {
61 self.as_node().children().all(|child| match *child.data() {
62 NodeData::Element(_) => false,
63 NodeData::Text(ref text) => text.borrow().is_empty(),
64 _ => true,
65 })
66 }
67 #[inline]
68 fn is_root(&self) -> bool {
69 match self.as_node().parent() {
70 None => false,
71 Some(parent) => matches!(*parent.data(), NodeData::Document(_)),
72 }
73 }
74
75 #[inline]
76 fn is_html_element_in_html_document(&self) -> bool {
77 self.name.ns == ns!(html)
84 }
85
86 #[inline]
87 fn has_local_name(&self, name: &LocalName) -> bool {
88 self.name.local == *name
89 }
90 #[inline]
91 fn has_namespace(&self, namespace: &Namespace) -> bool {
92 self.name.ns == *namespace
93 }
94
95 #[inline]
96 fn is_part(&self, _name: &LocalNameSelector) -> bool {
97 false
98 }
99
100 #[inline]
101 fn imported_part(&self, _: &LocalNameSelector) -> Option<LocalNameSelector> {
102 None
103 }
104
105 #[inline]
106 fn is_pseudo_element(&self) -> bool {
107 false
108 }
109
110 #[inline]
111 fn is_same_type(&self, other: &Self) -> bool {
112 self.name == other.name
113 }
114
115 #[inline]
116 fn is_link(&self) -> bool {
117 self.name.ns == ns!(html)
118 && matches!(
119 self.name.local,
120 local_name!("a") | local_name!("area") | local_name!("link")
121 )
122 && self
123 .attributes
124 .borrow()
125 .map
126 .contains_key(&ExpandedName::new(ns!(), local_name!("href")))
127 }
128
129 #[inline]
130 fn has_id(&self, id: &LocalNameSelector, case_sensitivity: CaseSensitivity) -> bool {
131 self.attributes
132 .borrow()
133 .get(local_name!("id"))
134 .is_some_and(|id_attr| case_sensitivity.eq(id.as_bytes(), id_attr.as_bytes()))
135 }
136
137 #[inline]
138 fn has_class(&self, name: &LocalNameSelector, case_sensitivity: CaseSensitivity) -> bool {
139 let name = name.as_bytes();
140 !name.is_empty()
141 && if let Some(class_attr) = self.attributes.borrow().get(local_name!("class")) {
142 class_attr
143 .split(SELECTOR_WHITESPACE)
144 .any(|class| case_sensitivity.eq(class.as_bytes(), name))
145 } else {
146 false
147 }
148 }
149
150 #[inline]
151 fn attr_matches(
152 &self,
153 ns: &NamespaceConstraint<&Namespace>,
154 local_name: &LocalNameSelector,
155 operation: &AttrSelectorOperation<&AttrValue>,
156 ) -> bool {
157 let attrs = self.attributes.borrow();
158 match *ns {
159 NamespaceConstraint::Any => attrs.map.iter().any(|(name, attr)| {
160 name.local == **local_name && operation.eval_str(attr.value.as_str())
161 }),
162 NamespaceConstraint::Specific(ns_url) => attrs
163 .map
164 .get(&ExpandedName::new(ns_url, (**local_name).clone()))
165 .is_some_and(|attr| operation.eval_str(attr.value.as_str())),
166 }
167 }
168
169 fn match_pseudo_element(
170 &self,
171 pseudo: &PseudoElement,
172 _context: &mut matching::MatchingContext<BrikSelectors>,
173 ) -> bool {
174 match *pseudo {}
175 }
176
177 fn match_non_ts_pseudo_class(
178 &self,
179 pseudo: &PseudoClass,
180 _context: &mut matching::MatchingContext<BrikSelectors>,
181 ) -> bool {
182 use self::PseudoClass::*;
183 match *pseudo {
184 Active | Focus | Hover | Enabled | Disabled | Checked | Indeterminate | Visited => {
185 false
186 }
187 AnyLink | Link => {
188 self.name.ns == ns!(html)
189 && matches!(
190 self.name.local,
191 local_name!("a") | local_name!("area") | local_name!("link")
192 )
193 && self.attributes.borrow().contains(local_name!("href"))
194 }
195 }
196 }
197
198 #[inline]
199 fn apply_selector_flags(&self, _flags: matching::ElementSelectorFlags) {
200 }
202
203 #[inline]
204 fn has_custom_state(&self, _name: &LocalNameSelector) -> bool {
205 false
207 }
208
209 #[inline]
210 fn add_element_unique_hashes(&self, filter: &mut selectors::bloom::BloomFilter) -> bool {
211 let _ = filter; false
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use crate::html5ever::tendril::TendrilSink;
219 use crate::parse_html;
220 use selectors::Element;
221
222 #[test]
226 fn parent_element() {
227 let html = "<div><p><span>text</span></p></div>";
228 let doc = parse_html().one(html);
229 let span = doc.select("span").unwrap().next().unwrap();
230
231 let parent = span.parent_element();
232 assert!(parent.is_some());
233 assert_eq!(parent.unwrap().name.local.as_ref(), "p");
234 }
235
236 #[test]
241 fn parent_element_none() {
242 let doc = parse_html().one("<html></html>");
243 let html = doc.select("html").unwrap().next().unwrap();
244
245 assert!(html.parent_element().is_none());
247 }
248
249 #[test]
254 fn prev_sibling_element() {
255 let html = "<div><p>1</p><span>2</span></div>";
256 let doc = parse_html().one(html);
257 let span = doc.select("span").unwrap().next().unwrap();
258
259 let prev = span.prev_sibling_element();
260 assert!(prev.is_some());
261 assert_eq!(prev.unwrap().name.local.as_ref(), "p");
262 }
263
264 #[test]
269 fn prev_sibling_element_none() {
270 let html = "<div><p>first</p></div>";
271 let doc = parse_html().one(html);
272 let p = doc.select("p").unwrap().next().unwrap();
273
274 assert!(p.prev_sibling_element().is_none());
275 }
276
277 #[test]
281 fn next_sibling_element() {
282 let html = "<div><p>1</p><span>2</span></div>";
283 let doc = parse_html().one(html);
284 let p = doc.select("p").unwrap().next().unwrap();
285
286 let next = p.next_sibling_element();
287 assert!(next.is_some());
288 assert_eq!(next.unwrap().name.local.as_ref(), "span");
289 }
290
291 #[test]
296 fn next_sibling_element_none() {
297 let html = "<div><p>last</p></div>";
298 let doc = parse_html().one(html);
299 let p = doc.select("p").unwrap().next().unwrap();
300
301 assert!(p.next_sibling_element().is_none());
302 }
303
304 #[test]
308 fn first_element_child() {
309 let html = "<div><p>first</p><span>second</span></div>";
310 let doc = parse_html().one(html);
311 let div = doc.select("div").unwrap().next().unwrap();
312
313 let first_child = div.first_element_child();
314 assert!(first_child.is_some());
315 assert_eq!(first_child.unwrap().name.local.as_ref(), "p");
316 }
317
318 #[test]
323 fn first_element_child_none() {
324 let html = "<div></div>";
325 let doc = parse_html().one(html);
326 let div = doc.select("div").unwrap().next().unwrap();
327
328 assert!(div.first_element_child().is_none());
329 }
330
331 #[test]
335 fn is_empty_true() {
336 let html = "<div></div>";
337 let doc = parse_html().one(html);
338 let div = doc.select("div").unwrap().next().unwrap();
339
340 assert!(div.is_empty());
341 }
342
343 #[test]
348 fn is_empty_false_with_element() {
349 let html = "<div><p>text</p></div>";
350 let doc = parse_html().one(html);
351 let div = doc.select("div").unwrap().next().unwrap();
352
353 assert!(!div.is_empty());
354 }
355
356 #[test]
361 fn is_empty_false_with_text() {
362 let html = "<div>text</div>";
363 let doc = parse_html().one(html);
364 let div = doc.select("div").unwrap().next().unwrap();
365
366 assert!(!div.is_empty());
367 }
368
369 #[test]
374 fn is_empty_true_with_empty_text() {
375 let html = "<div></div>";
376 let doc = parse_html().one(html);
377 let div = doc.select("div").unwrap().next().unwrap();
378
379 assert!(div.is_empty());
380 }
381
382 #[test]
387 fn is_root_true() {
388 let doc = parse_html().one("<html></html>");
389 let html = doc.select("html").unwrap().next().unwrap();
390
391 assert!(html.is_root());
392 }
393
394 #[test]
399 fn is_root_false() {
400 let html = "<html><body><div></div></body></html>";
401 let doc = parse_html().one(html);
402 let div = doc.select("div").unwrap().next().unwrap();
403
404 assert!(!div.is_root());
405 }
406
407 #[test]
412 fn is_html_element_in_html_document() {
413 let html = "<html><body><div></div></body></html>";
414 let doc = parse_html().one(html);
415 let div = doc.select("div").unwrap().next().unwrap();
416
417 assert!(div.is_html_element_in_html_document());
418 }
419
420 #[test]
425 fn has_local_name_true() {
426 let html = "<div></div>";
427 let doc = parse_html().one(html);
428 let div = doc.select("div").unwrap().next().unwrap();
429
430 assert!(div.has_local_name(&html5ever::local_name!("div")));
431 }
432
433 #[test]
438 fn has_local_name_false() {
439 let html = "<div></div>";
440 let doc = parse_html().one(html);
441 let div = doc.select("div").unwrap().next().unwrap();
442
443 assert!(!div.has_local_name(&html5ever::local_name!("span")));
444 }
445
446 #[test]
451 fn has_namespace_true() {
452 let html = "<div></div>";
453 let doc = parse_html().one(html);
454 let div = doc.select("div").unwrap().next().unwrap();
455
456 assert!(div.has_namespace(&html5ever::ns!(html)));
457 }
458
459 #[test]
464 fn is_same_type_true() {
465 let html = "<div></div><div></div>";
466 let doc = parse_html().one(html);
467 let mut divs = doc.select("div").unwrap();
468 let div1 = divs.next().unwrap();
469 let div2 = divs.next().unwrap();
470
471 assert!(div1.is_same_type(&div2));
472 }
473
474 #[test]
479 fn is_same_type_false() {
480 let html = "<div></div><span></span>";
481 let doc = parse_html().one(html);
482 let div = doc.select("div").unwrap().next().unwrap();
483 let span = doc.select("span").unwrap().next().unwrap();
484
485 assert!(!div.is_same_type(&span));
486 }
487
488 #[test]
492 fn is_link_true_anchor() {
493 let html = r#"<a href="https://example.com">link</a>"#;
494 let doc = parse_html().one(html);
495 let a = doc.select("a").unwrap().next().unwrap();
496
497 assert!(a.is_link());
498 }
499
500 #[test]
504 fn is_link_true_area() {
505 let html = r#"<map><area href="https://example.com"></map>"#;
506 let doc = parse_html().one(html);
507 let area = doc.select("area").unwrap().next().unwrap();
508
509 assert!(area.is_link());
510 }
511
512 #[test]
516 fn is_link_true_link() {
517 let html = r#"<link href="style.css">"#;
518 let doc = parse_html().one(html);
519 let link = doc.select("link").unwrap().next().unwrap();
520
521 assert!(link.is_link());
522 }
523
524 #[test]
529 fn is_link_false_no_href() {
530 let html = "<a>not a link</a>";
531 let doc = parse_html().one(html);
532 let a = doc.select("a").unwrap().next().unwrap();
533
534 assert!(!a.is_link());
535 }
536
537 #[test]
542 fn is_link_false_wrong_element() {
543 let html = r#"<div href="https://example.com">not a link</div>"#;
544 let doc = parse_html().one(html);
545 let div = doc.select("div").unwrap().next().unwrap();
546
547 assert!(!div.is_link());
548 }
549
550 #[test]
554 fn has_id_case_sensitive() {
555 let html = r#"<div id="myId"></div>"#;
556 let doc = parse_html().one(html);
557
558 assert!(doc.select("#myId").unwrap().next().is_some());
559 }
560
561 #[test]
565 fn has_id_not_found() {
566 let html = r#"<div id="myId"></div>"#;
567 let doc = parse_html().one(html);
568
569 assert!(doc.select("#otherId").unwrap().next().is_none());
570 }
571
572 #[test]
577 fn has_class_single() {
578 let html = r#"<div class="myClass"></div>"#;
579 let doc = parse_html().one(html);
580
581 assert!(doc.select(".myClass").unwrap().next().is_some());
582 }
583
584 #[test]
589 fn has_class_multiple() {
590 let html = r#"<div class="class1 class2 class3"></div>"#;
591 let doc = parse_html().one(html);
592
593 assert!(doc.select(".class2").unwrap().next().is_some());
594 }
595
596 #[test]
601 fn has_class_with_whitespace() {
602 let html = "<div class=\"class1 \t\n class2\"></div>";
603 let doc = parse_html().one(html);
604
605 assert!(doc.select(".class2").unwrap().next().is_some());
606 }
607
608 #[test]
613 fn has_class_not_found() {
614 let html = r#"<div class="myClass"></div>"#;
615 let doc = parse_html().one(html);
616
617 assert!(doc.select(".otherClass").unwrap().next().is_none());
618 }
619
620 #[test]
625 fn has_class_no_class_attr() {
626 let html = "<div></div>";
627 let doc = parse_html().one(html);
628
629 assert!(doc.select(".myClass").unwrap().next().is_none());
630 }
631
632 #[test]
637 fn attr_matches_exists() {
638 let html = r#"<div data-value="test"></div>"#;
639 let doc = parse_html().one(html);
640
641 assert!(doc.select("[data-value]").unwrap().next().is_some());
642 }
643
644 #[test]
649 fn attr_matches_exact_value() {
650 let html = r#"<div data-value="test"></div>"#;
651 let doc = parse_html().one(html);
652
653 assert!(doc
654 .select(r#"[data-value="test"]"#)
655 .unwrap()
656 .next()
657 .is_some());
658 }
659
660 #[test]
665 fn attr_matches_not_found() {
666 let html = r#"<div data-value="test"></div>"#;
667 let doc = parse_html().one(html);
668
669 assert!(doc
670 .select(r#"[data-value="other"]"#)
671 .unwrap()
672 .next()
673 .is_none());
674 }
675
676 #[test]
681 fn attr_matches_contains() {
682 let html = r#"<div data-value="hello world"></div>"#;
683 let doc = parse_html().one(html);
684
685 assert!(doc
686 .select(r#"[data-value*="world"]"#)
687 .unwrap()
688 .next()
689 .is_some());
690 }
691
692 #[test]
697 fn attr_matches_starts_with() {
698 let html = r#"<div data-value="hello world"></div>"#;
699 let doc = parse_html().one(html);
700
701 assert!(doc
702 .select(r#"[data-value^="hello"]"#)
703 .unwrap()
704 .next()
705 .is_some());
706 }
707
708 #[test]
713 fn attr_matches_ends_with() {
714 let html = r#"<div data-value="hello world"></div>"#;
715 let doc = parse_html().one(html);
716
717 assert!(doc
718 .select(r#"[data-value$="world"]"#)
719 .unwrap()
720 .next()
721 .is_some());
722 }
723
724 #[test]
729 fn is_pseudo_element_false() {
730 let html = "<div></div>";
731 let doc = parse_html().one(html);
732 let div = doc.select("div").unwrap().next().unwrap();
733
734 assert!(!div.is_pseudo_element());
735 }
736
737 #[test]
742 fn is_html_slot_element_false() {
743 let html = "<slot></slot>";
744 let doc = parse_html().one(html);
745 let slot = doc.select("slot").unwrap().next().unwrap();
746
747 assert!(!slot.is_html_slot_element());
748 }
749
750 #[test]
755 fn parent_node_is_shadow_root_false() {
756 let html = "<div><p>text</p></div>";
757 let doc = parse_html().one(html);
758 let p = doc.select("p").unwrap().next().unwrap();
759
760 assert!(!p.parent_node_is_shadow_root());
761 }
762
763 #[test]
768 fn containing_shadow_host_none() {
769 let html = "<div></div>";
770 let doc = parse_html().one(html);
771 let div = doc.select("div").unwrap().next().unwrap();
772
773 assert!(div.containing_shadow_host().is_none());
774 }
775
776 #[test]
781 fn is_part_false() {
782 let html = "<div></div>";
783 let doc = parse_html().one(html);
784 let div = doc.select("div").unwrap().next().unwrap();
785
786 assert!(!div.is_part(&html5ever::local_name!("div").into()));
787 }
788
789 #[test]
794 fn imported_part_none() {
795 let html = "<div></div>";
796 let doc = parse_html().one(html);
797 let div = doc.select("div").unwrap().next().unwrap();
798
799 assert!(div
800 .imported_part(&html5ever::local_name!("div").into())
801 .is_none());
802 }
803
804 #[test]
809 fn has_custom_state_false() {
810 let html = "<div></div>";
811 let doc = parse_html().one(html);
812 let div = doc.select("div").unwrap().next().unwrap();
813
814 assert!(!div.has_custom_state(&html5ever::local_name!("div").into()));
815 }
816
817 #[test]
822 fn pseudo_class_link() {
823 let html = r#"<a href="https://example.com">link</a><a>no href</a>"#;
824 let doc = parse_html().one(html);
825
826 let links: Vec<_> = doc.select("a:link").unwrap().collect();
827 assert_eq!(links.len(), 1);
828 }
829
830 #[test]
835 fn pseudo_class_any_link() {
836 let html = r#"<a href="https://example.com">link</a><link href="style.css">"#;
837 let doc = parse_html().one(html);
838
839 let links: Vec<_> = doc.select(":any-link").unwrap().collect();
840 assert_eq!(links.len(), 2);
841 }
842
843 #[test]
848 fn has_namespace_false() {
849 let html = "<div></div>";
850 let doc = parse_html().one(html);
851 let div = doc.select("div").unwrap().next().unwrap();
852
853 assert!(!div.has_namespace(&html5ever::ns!(svg)));
855 }
856
857 #[test]
862 fn attr_matches_any_namespace_constraint() {
863 let html = r#"<div data-value="test"></div>"#;
864 let doc = parse_html().one(html);
865 let div = doc.select("div").unwrap().next().unwrap();
866
867 let attrs = div.attributes.borrow();
869 assert!(attrs.contains("data-value"));
870 }
871}