accessibility_scraper/element_ref/
element.rs1use super::ElementRef;
2use crate::selector::{CssLocalName, CssString, NonTSPseudoClass, PseudoElement, Simple};
3use fast_html5ever::{LocalName, Namespace};
4use selectors::{
5 attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint},
6 matching, Element, OpaqueElement,
7};
8
9impl<'a> Element for ElementRef<'a> {
11 type Impl = Simple;
12
13 #[inline]
14 fn is_part(&self, _name: &LocalName) -> bool {
15 false
16 }
17
18 #[inline]
19 fn imported_part(&self, _: &LocalName) -> Option<LocalName> {
20 None
21 }
22
23 #[inline]
24 fn exported_part(&self, _: &LocalName) -> Option<LocalName> {
25 None
26 }
27
28 #[inline]
29 fn is_same_type(&self, other: &Self) -> bool {
30 self.value().name == other.value().name
31 }
32
33 #[inline]
34 fn is_pseudo_element(&self) -> bool {
35 false
36 }
37
38 #[inline]
39 fn has_local_name(&self, name: &CssLocalName) -> bool {
40 self.value().name.local == *name.0
41 }
42
43 #[inline]
44 fn has_namespace(&self, namespace: &Namespace) -> bool {
45 &self.value().name.ns == *&namespace
46 }
47
48 fn opaque(&self) -> OpaqueElement {
49 OpaqueElement::new(self.node.value())
50 }
51
52 fn parent_element(&self) -> Option<Self> {
53 self.parent().and_then(ElementRef::wrap)
54 }
55
56 fn parent_node_is_shadow_root(&self) -> bool {
57 false
58 }
59
60 fn containing_shadow_host(&self) -> Option<Self> {
61 None
62 }
63
64 fn prev_sibling_element(&self) -> Option<Self> {
65 self.prev_siblings()
66 .find(|sibling| sibling.value().is_element())
67 .map(ElementRef::new)
68 }
69
70 fn next_sibling_element(&self) -> Option<Self> {
71 self.next_siblings()
72 .find(|sibling| sibling.value().is_element())
73 .map(ElementRef::new)
74 }
75
76 fn is_html_element_in_html_document(&self) -> bool {
77 self.value().name.ns == ns!(html)
79 }
80
81 fn attr_matches(
82 &self,
83 ns: &NamespaceConstraint<&Namespace>,
84 local_name: &CssLocalName,
85 operation: &AttrSelectorOperation<&CssString>,
86 ) -> bool {
87 self.value().attrs.iter().any(|(key, value)| {
88 !matches!(*ns, NamespaceConstraint::Specific(url) if *url != key.ns)
89 && local_name.0 == key.local
90 && operation.eval_str(value)
91 })
92 }
93
94 fn match_non_ts_pseudo_class<F>(
95 &self,
96 pseudo_class: &NonTSPseudoClass,
97 _context: &mut selectors::matching::MatchingContext<Self::Impl>,
98 _flags_setter: &mut F,
99 ) -> bool
100 where
101 F: FnMut(&Self, selectors::matching::ElementSelectorFlags),
102 {
103 use self::NonTSPseudoClass::*;
104
105 match *pseudo_class {
106 Active | Focus | Hover | Enabled | Disabled | Checked | Indeterminate | Visited => {
107 false
108 }
109 AnyLink | Link => {
110 self.value().name.ns == ns!(html)
111 && matches!(
112 self.value().name.local,
113 local_name!("a") | local_name!("area") | local_name!("link")
114 )
115 && self.has_attribute("href")
116 }
117 }
118 }
119
120 fn match_pseudo_element(
121 &self,
122 _pe: &PseudoElement,
123 _context: &mut matching::MatchingContext<Self::Impl>,
124 ) -> bool {
125 false
126 }
127
128 fn is_link(&self) -> bool {
129 self.value().name() == "link"
130 }
131
132 fn is_html_slot_element(&self) -> bool {
133 true
134 }
135
136 fn has_id(&self, id: &CssLocalName, case_sensitivity: CaseSensitivity) -> bool {
137 match self.value().id() {
138 Some(val) => case_sensitivity.eq(id.0.as_bytes(), val.as_bytes()),
139 None => false,
140 }
141 }
142
143 fn has_class(&self, name: &CssLocalName, case_sensitivity: CaseSensitivity) -> bool {
144 self.value().has_class(&name.0, case_sensitivity)
145 }
146
147 fn is_empty(&self) -> bool {
148 !self
149 .children()
150 .any(|child| child.value().is_element() || child.value().is_text())
151 }
152
153 fn is_root(&self) -> bool {
154 self.parent()
155 .map_or(false, |parent| parent.value().is_document())
156 }
157 }
159
160#[cfg(test)]
161mod tests {
162 use crate::html::Html;
163 use crate::selector::{CssLocalName, Selector};
164 use selectors::attr::CaseSensitivity;
165 use selectors::Element;
166
167 #[test]
168 fn test_has_id() {
169 let html = "<p id='link_id_456'>hey there</p>";
170 let fragment = Html::parse_fragment(html);
171 let sel = Selector::parse("p").unwrap();
172
173 let element = fragment.select(&sel).next().unwrap();
174 assert!(element.has_id(
175 &CssLocalName::from("link_id_456"),
176 CaseSensitivity::CaseSensitive
177 ));
178
179 let html = "<p>hey there</p>";
180 let fragment = Html::parse_fragment(html);
181 let element = fragment.select(&sel).next().unwrap();
182 assert!(!element.has_id(
183 &CssLocalName::from("any_link_id"),
184 CaseSensitivity::CaseSensitive
185 ));
186 }
187
188 #[test]
189 fn test_is_link() {
190 let html = "<link href='https://www.example.com'>";
191 let fragment = Html::parse_fragment(html);
192 let sel = Selector::parse("link").unwrap();
193 let element = fragment.select(&sel).next().unwrap();
194 assert!(element.is_link());
195
196 let html = "<p>hey there</p>";
197 let fragment = Html::parse_fragment(html);
198 let sel = Selector::parse("p").unwrap();
199 let element = fragment.select(&sel).next().unwrap();
200 assert!(!element.is_link());
201 }
202
203 #[test]
204 fn test_has_class() {
205 let html = "<p class='my_class'>hey there</p>";
206 let fragment = Html::parse_fragment(html);
207 let sel = Selector::parse("p").unwrap();
208 let element = fragment.select(&sel).next().unwrap();
209 assert!(element.has_class(
210 &CssLocalName::from("my_class"),
211 CaseSensitivity::CaseSensitive
212 ));
213
214 let html = "<p>hey there</p>";
215 let fragment = Html::parse_fragment(html);
216 let sel = Selector::parse("p").unwrap();
217 let element = fragment.select(&sel).next().unwrap();
218 assert!(!element.has_class(
219 &CssLocalName::from("my_class"),
220 CaseSensitivity::CaseSensitive
221 ));
222 }
223}