script/
xpath.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Bindings to the `xpath` crate
6
7use std::cell::Ref;
8use std::cmp::Ordering;
9use std::fmt::Debug;
10use std::hash::Hash;
11use std::rc::Rc;
12
13use html5ever::{LocalName, Namespace, Prefix};
14use script_bindings::callback::ExceptionHandling;
15use script_bindings::codegen::GenericBindings::AttrBinding::AttrMethods;
16use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
17use script_bindings::root::Dom;
18use script_bindings::script_runtime::CanGc;
19use script_bindings::str::DOMString;
20use style::Atom;
21
22use crate::dom::attr::Attr;
23use crate::dom::bindings::codegen::Bindings::XPathNSResolverBinding::XPathNSResolver;
24use crate::dom::bindings::error::Error;
25use crate::dom::bindings::inheritance::Castable;
26use crate::dom::bindings::root::DomRoot;
27use crate::dom::comment::Comment;
28use crate::dom::document::Document;
29use crate::dom::element::Element;
30use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
31use crate::dom::processinginstruction::ProcessingInstruction;
32use crate::dom::text::Text;
33
34pub(crate) type Value = xpath::Value<XPathWrapper<DomRoot<Node>>>;
35
36/// Wrapper type that allows us to define xpath traits on the relevant types,
37/// since they're not defined in `script`.
38#[derive(Clone, Debug, Eq, PartialEq)]
39pub(crate) struct XPathWrapper<T>(pub T);
40
41pub(crate) struct XPathImplementation;
42
43impl xpath::Dom for XPathImplementation {
44    type Node = XPathWrapper<DomRoot<Node>>;
45    type JsError = Error;
46    type NamespaceResolver = XPathWrapper<Rc<XPathNSResolver>>;
47}
48
49impl xpath::Node for XPathWrapper<DomRoot<Node>> {
50    type ProcessingInstruction = XPathWrapper<DomRoot<ProcessingInstruction>>;
51    type Document = XPathWrapper<DomRoot<Document>>;
52    type Attribute = XPathWrapper<DomRoot<Attr>>;
53    type Element = XPathWrapper<DomRoot<Element>>;
54
55    fn is_comment(&self) -> bool {
56        self.0.is::<Comment>()
57    }
58
59    fn is_text(&self) -> bool {
60        self.0.is::<Text>()
61    }
62
63    fn text_content(&self) -> String {
64        self.0.GetTextContent().unwrap_or_default().into()
65    }
66
67    fn language(&self) -> Option<String> {
68        self.0.get_lang()
69    }
70
71    fn parent(&self) -> Option<Self> {
72        // The parent of an attribute node is its owner, see
73        // https://www.w3.org/TR/1999/REC-xpath-19991116/#attribute-nodes
74        if let Some(attribute) = self.0.downcast::<Attr>() {
75            return attribute
76                .GetOwnerElement()
77                .map(DomRoot::upcast)
78                .map(XPathWrapper);
79        }
80
81        self.0.GetParentNode().map(XPathWrapper)
82    }
83
84    fn children(&self) -> impl Iterator<Item = Self> {
85        self.0.children().map(XPathWrapper)
86    }
87
88    fn compare_tree_order(&self, other: &Self) -> Ordering {
89        if self == other {
90            Ordering::Equal
91        } else if self.0.is_before(&other.0) {
92            Ordering::Less
93        } else {
94            Ordering::Greater
95        }
96    }
97
98    fn traverse_preorder(&self) -> impl Iterator<Item = Self> {
99        self.0
100            .traverse_preorder(ShadowIncluding::No)
101            .map(XPathWrapper)
102    }
103
104    fn inclusive_ancestors(&self) -> impl Iterator<Item = Self> {
105        self.0
106            .inclusive_ancestors(ShadowIncluding::No)
107            .map(XPathWrapper)
108    }
109
110    fn preceding_nodes(&self, root: &Self) -> impl Iterator<Item = Self> {
111        self.0.preceding_nodes(&root.0).map(XPathWrapper)
112    }
113
114    fn following_nodes(&self, root: &Self) -> impl Iterator<Item = Self> {
115        self.0.following_nodes(&root.0).map(XPathWrapper)
116    }
117
118    fn preceding_siblings(&self) -> impl Iterator<Item = Self> {
119        self.0.preceding_siblings().map(XPathWrapper)
120    }
121
122    fn following_siblings(&self) -> impl Iterator<Item = Self> {
123        self.0.following_siblings().map(XPathWrapper)
124    }
125
126    fn owner_document(&self) -> Self::Document {
127        XPathWrapper(self.0.owner_document())
128    }
129
130    fn to_opaque(&self) -> impl Eq + Hash {
131        self.0.to_opaque()
132    }
133
134    fn as_processing_instruction(&self) -> Option<Self::ProcessingInstruction> {
135        self.0
136            .downcast::<ProcessingInstruction>()
137            .map(DomRoot::from_ref)
138            .map(XPathWrapper)
139    }
140
141    fn as_attribute(&self) -> Option<Self::Attribute> {
142        self.0
143            .downcast::<Attr>()
144            .map(DomRoot::from_ref)
145            .map(XPathWrapper)
146    }
147
148    fn as_element(&self) -> Option<Self::Element> {
149        self.0
150            .downcast::<Element>()
151            .map(DomRoot::from_ref)
152            .map(XPathWrapper)
153    }
154
155    fn lookup_namespace_uri(&self, uri: Option<&str>) -> Option<String> {
156        self.0
157            .LookupNamespaceURI(uri.map(DOMString::from))
158            .map(String::from)
159    }
160}
161
162impl xpath::Document for XPathWrapper<DomRoot<Document>> {
163    type Node = XPathWrapper<DomRoot<Node>>;
164
165    fn is_html_document(&self) -> bool {
166        self.0.is_html_document()
167    }
168
169    fn get_elements_with_id(
170        &self,
171        id: &str,
172    ) -> impl Iterator<Item = XPathWrapper<DomRoot<Element>>> {
173        struct ElementIterator<'a> {
174            elements: Ref<'a, [Dom<Element>]>,
175            position: usize,
176        }
177
178        impl<'a> Iterator for ElementIterator<'a> {
179            type Item = XPathWrapper<DomRoot<Element>>;
180
181            fn next(&mut self) -> Option<Self::Item> {
182                let element = self.elements.get(self.position)?;
183                self.position += 1;
184                Some(element.as_rooted().into())
185            }
186        }
187
188        ElementIterator {
189            elements: self.0.get_elements_with_id(&Atom::from(id)),
190            position: 0,
191        }
192    }
193}
194
195impl xpath::Element for XPathWrapper<DomRoot<Element>> {
196    type Node = XPathWrapper<DomRoot<Node>>;
197    type Attribute = XPathWrapper<DomRoot<Attr>>;
198
199    fn as_node(&self) -> Self::Node {
200        DomRoot::from_ref(self.0.upcast::<Node>()).into()
201    }
202
203    fn attributes(&self) -> impl Iterator<Item = Self::Attribute> {
204        struct AttributeIterator<'a> {
205            attributes: Ref<'a, [Dom<Attr>]>,
206            position: usize,
207        }
208
209        impl<'a> Iterator for AttributeIterator<'a> {
210            type Item = XPathWrapper<DomRoot<Attr>>;
211
212            fn next(&mut self) -> Option<Self::Item> {
213                let attribute = self.attributes.get(self.position)?;
214                self.position += 1;
215                Some(attribute.as_rooted().into())
216            }
217
218            fn size_hint(&self) -> (usize, Option<usize>) {
219                let exact_length = self.attributes.len() - self.position;
220                (exact_length, Some(exact_length))
221            }
222        }
223
224        AttributeIterator {
225            attributes: self.0.attrs(),
226            position: 0,
227        }
228    }
229
230    fn prefix(&self) -> Option<Prefix> {
231        self.0.prefix().clone()
232    }
233
234    fn namespace(&self) -> Namespace {
235        self.0.namespace().clone()
236    }
237
238    fn local_name(&self) -> LocalName {
239        self.0.local_name().clone()
240    }
241}
242
243impl xpath::Attribute for XPathWrapper<DomRoot<Attr>> {
244    type Node = XPathWrapper<DomRoot<Node>>;
245
246    fn as_node(&self) -> Self::Node {
247        XPathWrapper(DomRoot::from_ref(self.0.upcast::<Node>()))
248    }
249
250    fn prefix(&self) -> Option<Prefix> {
251        self.0.prefix().cloned()
252    }
253
254    fn namespace(&self) -> Namespace {
255        self.0.namespace().clone()
256    }
257
258    fn local_name(&self) -> LocalName {
259        self.0.local_name().clone()
260    }
261}
262
263impl xpath::NamespaceResolver<Error> for XPathWrapper<Rc<XPathNSResolver>> {
264    fn resolve_namespace_prefix(&self, prefix: Option<&str>) -> Result<Option<String>, Error> {
265        self.0
266            .LookupNamespaceURI__(
267                prefix.map(DOMString::from),
268                ExceptionHandling::Rethrow,
269                CanGc::note(),
270            )
271            .map(|result| result.map(String::from))
272    }
273}
274
275impl xpath::ProcessingInstruction for XPathWrapper<DomRoot<ProcessingInstruction>> {
276    fn target(&self) -> String {
277        self.0.target().to_owned().into()
278    }
279}
280
281impl<T> From<T> for XPathWrapper<T> {
282    fn from(value: T) -> Self {
283        Self(value)
284    }
285}
286
287impl<T> XPathWrapper<T> {
288    pub(crate) fn into_inner(self) -> T {
289        self.0
290    }
291}