sws_scraper/element_ref/
element.rs

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