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::{GetRootNodeOptions, 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, Fallible};
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 NamespaceResolver = XPathWrapper<Rc<XPathNSResolver>>;
46}
47
48impl xpath::Node for XPathWrapper<DomRoot<Node>> {
49    type ProcessingInstruction = XPathWrapper<DomRoot<ProcessingInstruction>>;
50    type Document = XPathWrapper<DomRoot<Document>>;
51    type Attribute = XPathWrapper<DomRoot<Attr>>;
52    type Element = XPathWrapper<DomRoot<Element>>;
53
54    fn is_comment(&self) -> bool {
55        self.0.is::<Comment>()
56    }
57
58    fn is_text(&self) -> bool {
59        self.0.is::<Text>()
60    }
61
62    fn text_content(&self) -> String {
63        self.0.GetTextContent().unwrap_or_default().into()
64    }
65
66    fn language(&self) -> Option<String> {
67        self.0.get_lang()
68    }
69
70    fn parent(&self) -> Option<Self> {
71        // The parent of an attribute node is its owner, see
72        // https://www.w3.org/TR/1999/REC-xpath-19991116/#attribute-nodes
73        if let Some(attribute) = self.0.downcast::<Attr>() {
74            return attribute
75                .GetOwnerElement()
76                .map(DomRoot::upcast)
77                .map(XPathWrapper);
78        }
79
80        self.0.GetParentNode().map(XPathWrapper)
81    }
82
83    fn children(&self) -> impl Iterator<Item = Self> {
84        self.0.children().map(XPathWrapper)
85    }
86
87    fn compare_tree_order(&self, other: &Self) -> Ordering {
88        if self == other {
89            Ordering::Equal
90        } else if self.0.is_before(&other.0) {
91            Ordering::Less
92        } else {
93            Ordering::Greater
94        }
95    }
96
97    fn traverse_preorder(&self) -> impl Iterator<Item = Self> {
98        self.0
99            .traverse_preorder(ShadowIncluding::No)
100            .map(XPathWrapper)
101    }
102
103    fn inclusive_ancestors(&self) -> impl Iterator<Item = Self> {
104        self.0
105            .inclusive_ancestors(ShadowIncluding::No)
106            .map(XPathWrapper)
107    }
108
109    fn preceding_nodes(&self, root: &Self) -> impl Iterator<Item = Self> {
110        self.0.preceding_nodes(&root.0).map(XPathWrapper)
111    }
112
113    fn following_nodes(&self, root: &Self) -> impl Iterator<Item = Self> {
114        self.0.following_nodes(&root.0).map(XPathWrapper)
115    }
116
117    fn preceding_siblings(&self) -> impl Iterator<Item = Self> {
118        self.0.preceding_siblings().map(XPathWrapper)
119    }
120
121    fn following_siblings(&self) -> impl Iterator<Item = Self> {
122        self.0.following_siblings().map(XPathWrapper)
123    }
124
125    fn owner_document(&self) -> Self::Document {
126        XPathWrapper(self.0.owner_document())
127    }
128
129    fn to_opaque(&self) -> impl Eq + Hash {
130        self.0.to_opaque()
131    }
132
133    fn as_processing_instruction(&self) -> Option<Self::ProcessingInstruction> {
134        self.0
135            .downcast::<ProcessingInstruction>()
136            .map(DomRoot::from_ref)
137            .map(XPathWrapper)
138    }
139
140    fn as_attribute(&self) -> Option<Self::Attribute> {
141        self.0
142            .downcast::<Attr>()
143            .map(DomRoot::from_ref)
144            .map(XPathWrapper)
145    }
146
147    fn as_element(&self) -> Option<Self::Element> {
148        self.0
149            .downcast::<Element>()
150            .map(DomRoot::from_ref)
151            .map(XPathWrapper)
152    }
153
154    fn get_root_node(&self) -> Self {
155        XPathWrapper(self.0.GetRootNode(&GetRootNodeOptions::empty()))
156    }
157}
158
159impl xpath::Document for XPathWrapper<DomRoot<Document>> {
160    type Node = XPathWrapper<DomRoot<Node>>;
161
162    fn get_elements_with_id(
163        &self,
164        id: &str,
165    ) -> impl Iterator<Item = XPathWrapper<DomRoot<Element>>> {
166        struct ElementIterator<'a> {
167            elements: Ref<'a, [Dom<Element>]>,
168            position: usize,
169        }
170
171        impl<'a> Iterator for ElementIterator<'a> {
172            type Item = XPathWrapper<DomRoot<Element>>;
173
174            fn next(&mut self) -> Option<Self::Item> {
175                let element = self.elements.get(self.position)?;
176                self.position += 1;
177                Some(element.as_rooted().into())
178            }
179        }
180
181        ElementIterator {
182            elements: self.0.get_elements_with_id(&Atom::from(id)),
183            position: 0,
184        }
185    }
186}
187
188impl xpath::Element for XPathWrapper<DomRoot<Element>> {
189    type Node = XPathWrapper<DomRoot<Node>>;
190    type Attribute = XPathWrapper<DomRoot<Attr>>;
191
192    fn as_node(&self) -> Self::Node {
193        DomRoot::from_ref(self.0.upcast::<Node>()).into()
194    }
195
196    fn attributes(&self) -> impl Iterator<Item = Self::Attribute> {
197        struct AttributeIterator<'a> {
198            attributes: Ref<'a, [Dom<Attr>]>,
199            position: usize,
200        }
201
202        impl<'a> Iterator for AttributeIterator<'a> {
203            type Item = XPathWrapper<DomRoot<Attr>>;
204
205            fn next(&mut self) -> Option<Self::Item> {
206                let attribute = self.attributes.get(self.position)?;
207                self.position += 1;
208                Some(attribute.as_rooted().into())
209            }
210
211            fn size_hint(&self) -> (usize, Option<usize>) {
212                let exact_length = self.attributes.len() - self.position;
213                (exact_length, Some(exact_length))
214            }
215        }
216
217        AttributeIterator {
218            attributes: self.0.attrs(),
219            position: 0,
220        }
221    }
222
223    fn prefix(&self) -> Option<Prefix> {
224        self.0.prefix().clone()
225    }
226
227    fn namespace(&self) -> Namespace {
228        self.0.namespace().clone()
229    }
230
231    fn local_name(&self) -> LocalName {
232        self.0.local_name().clone()
233    }
234
235    fn is_html_element_in_html_document(&self) -> bool {
236        self.0.is_html_element() && self.0.owner_document().is_html_document()
237    }
238}
239
240impl xpath::Attribute for XPathWrapper<DomRoot<Attr>> {
241    type Node = XPathWrapper<DomRoot<Node>>;
242
243    fn as_node(&self) -> Self::Node {
244        XPathWrapper(DomRoot::from_ref(self.0.upcast::<Node>()))
245    }
246
247    fn prefix(&self) -> Option<Prefix> {
248        self.0.prefix().cloned()
249    }
250
251    fn namespace(&self) -> Namespace {
252        self.0.namespace().clone()
253    }
254
255    fn local_name(&self) -> LocalName {
256        self.0.local_name().clone()
257    }
258}
259
260impl xpath::NamespaceResolver for XPathWrapper<Rc<XPathNSResolver>> {
261    fn resolve_namespace_prefix(&self, prefix: &str) -> Option<String> {
262        self.0
263            .LookupNamespaceURI__(
264                Some(DOMString::from(prefix)),
265                ExceptionHandling::Report,
266                CanGc::note(),
267            )
268            .ok()
269            .flatten()
270            .map(String::from)
271    }
272}
273
274impl xpath::ProcessingInstruction for XPathWrapper<DomRoot<ProcessingInstruction>> {
275    fn target(&self) -> String {
276        self.0.target().to_owned().into()
277    }
278}
279
280impl<T> From<T> for XPathWrapper<T> {
281    fn from(value: T) -> Self {
282        Self(value)
283    }
284}
285
286impl<T> XPathWrapper<T> {
287    pub(crate) fn into_inner(self) -> T {
288        self.0
289    }
290}
291
292pub(crate) fn parse_expression(
293    expression: &str,
294    resolver: Option<Rc<XPathNSResolver>>,
295    is_in_html_document: bool,
296) -> Fallible<xpath::Expression> {
297    xpath::parse(expression, resolver.map(XPathWrapper), is_in_html_document).map_err(|error| {
298        match error {
299            xpath::ParserError::FailedToResolveNamespacePrefix => Error::Namespace,
300            _ => Error::Syntax(Some(format!("Failed to parse XPath expression: {error:?}"))),
301        }
302    })
303}