/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Bindings to the `xpath` crate use std::cell::Ref; use std::cmp::Ordering; use std::fmt::Debug; use std::rc::Rc; use html5ever::{LocalName, Namespace, Prefix}; use script_bindings::callback::ExceptionHandling; use script_bindings::codegen::GenericBindings::AttrBinding::AttrMethods; use script_bindings::codegen::GenericBindings::NodeBinding::{GetRootNodeOptions, NodeMethods}; use script_bindings::root::Dom; use script_bindings::script_runtime::CanGc; use script_bindings::str::DOMString; use style::Atom; use style::dom::OpaqueNode; use crate::dom::attr::Attr; use crate::dom::bindings::codegen::Bindings::XPathNSResolverBinding::XPathNSResolver; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::DomRoot; use crate::dom::comment::Comment; use crate::dom::document::Document; use crate::dom::element::Element; use crate::dom::node::{Node, NodeTraits, PrecedingNodeIterator, ShadowIncluding}; use crate::dom::processinginstruction::ProcessingInstruction; use crate::dom::text::Text; pub(crate) type Value = xpath::Value>>; /// Wrapper type that allows us to define xpath traits on the relevant types, /// since they're not defined in `script`. #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct XPathWrapper(pub T); pub(crate) struct XPathImplementation; impl xpath::Dom for XPathImplementation { type Node = XPathWrapper>; type NamespaceResolver = XPathWrapper>; } impl xpath::Node for XPathWrapper> { type ProcessingInstruction = XPathWrapper>; type Document = XPathWrapper>; type Attribute = XPathWrapper>; type Element = XPathWrapper>; /// A opaque handle to a node with the sole purpose of comparing one node with another. type Opaque = OpaqueNode; fn is_comment(&self) -> bool { self.0.is::() } fn is_text(&self) -> bool { self.0.is::() } fn text_content(&self) -> String { self.0.GetTextContent().unwrap_or_default().into() } fn language(&self) -> Option { self.0.get_lang() } fn parent(&self) -> Option { // The parent of an attribute node is its owner, see // https://www.w3.org/TR/1999/REC-xpath-19991116/#attribute-nodes if let Some(attribute) = self.0.downcast::() { return attribute .GetOwnerElement() .map(DomRoot::upcast) .map(XPathWrapper); } self.0.GetParentNode().map(XPathWrapper) } fn children(&self) -> impl Iterator { self.0.children().map(XPathWrapper) } fn compare_tree_order(&self, other: &Self) -> Ordering { if self == other { Ordering::Equal } else if self.0.is_before(&other.0) { Ordering::Less } else { Ordering::Greater } } fn traverse_preorder(&self) -> impl Iterator { self.0 .traverse_preorder(ShadowIncluding::No) .map(XPathWrapper) } fn inclusive_ancestors(&self) -> impl Iterator { self.0 .inclusive_ancestors(ShadowIncluding::No) .map(XPathWrapper) } fn preceding_nodes(&self) -> impl Iterator { PrecedingNodeIteratorWithoutAncestors::new(&self.0).map(XPathWrapper) } fn following_nodes(&self) -> impl Iterator { let owner_document = self.0.owner_document(); let next_non_descendant_node = self .0 .following_nodes(owner_document.upcast()) .next_skipping_children(); let following_nodes = next_non_descendant_node .clone() .map(|node| node.following_nodes(owner_document.upcast())) .into_iter() .flatten(); next_non_descendant_node .into_iter() .chain(following_nodes) .map(XPathWrapper) } fn preceding_siblings(&self) -> impl Iterator { self.0.preceding_siblings().map(XPathWrapper) } fn following_siblings(&self) -> impl Iterator { self.0.following_siblings().map(XPathWrapper) } fn owner_document(&self) -> Self::Document { XPathWrapper(self.0.owner_document()) } fn to_opaque(&self) -> Self::Opaque { self.0.to_opaque() } fn as_processing_instruction(&self) -> Option { self.0 .downcast::() .map(DomRoot::from_ref) .map(XPathWrapper) } fn as_attribute(&self) -> Option { self.0 .downcast::() .map(DomRoot::from_ref) .map(XPathWrapper) } fn as_element(&self) -> Option { self.0 .downcast::() .map(DomRoot::from_ref) .map(XPathWrapper) } fn get_root_node(&self) -> Self { XPathWrapper(self.0.GetRootNode(&GetRootNodeOptions::empty())) } } impl xpath::Document for XPathWrapper> { type Node = XPathWrapper>; fn get_elements_with_id( &self, id: &str, ) -> impl Iterator>> { struct ElementIterator<'a> { elements: Ref<'a, [Dom]>, position: usize, } impl<'a> Iterator for ElementIterator<'a> { type Item = XPathWrapper>; fn next(&mut self) -> Option { let element = self.elements.get(self.position)?; self.position += 1; Some(element.as_rooted().into()) } } ElementIterator { elements: self.0.get_elements_with_id(&Atom::from(id)), position: 0, } } } impl xpath::Element for XPathWrapper> { type Node = XPathWrapper>; type Attribute = XPathWrapper>; fn as_node(&self) -> Self::Node { DomRoot::from_ref(self.0.upcast::()).into() } fn attributes(&self) -> impl Iterator { struct AttributeIterator<'a> { attributes: Ref<'a, [Dom]>, position: usize, } impl<'a> Iterator for AttributeIterator<'a> { type Item = XPathWrapper>; fn next(&mut self) -> Option { let attribute = self.attributes.get(self.position)?; self.position += 1; Some(attribute.as_rooted().into()) } fn size_hint(&self) -> (usize, Option) { let exact_length = self.attributes.len() - self.position; (exact_length, Some(exact_length)) } } AttributeIterator { attributes: self.0.attrs(), position: 0, } } fn prefix(&self) -> Option { self.0.prefix().clone() } fn namespace(&self) -> Namespace { self.0.namespace().clone() } fn local_name(&self) -> LocalName { self.0.local_name().clone() } fn is_html_element_in_html_document(&self) -> bool { self.0.is_html_element() && self.0.owner_document().is_html_document() } } impl xpath::Attribute for XPathWrapper> { type Node = XPathWrapper>; fn as_node(&self) -> Self::Node { XPathWrapper(DomRoot::from_ref(self.0.upcast::())) } fn prefix(&self) -> Option { self.0.prefix().cloned() } fn namespace(&self) -> Namespace { self.0.namespace().clone() } fn local_name(&self) -> LocalName { self.0.local_name().clone() } } impl xpath::NamespaceResolver for XPathWrapper> { fn resolve_namespace_prefix(&self, prefix: &str) -> Option { self.0 .LookupNamespaceURI__( Some(DOMString::from(prefix)), ExceptionHandling::Report, CanGc::deprecated_note(), ) .ok() .flatten() .map(String::from) } } impl xpath::ProcessingInstruction for XPathWrapper> { fn target(&self) -> String { self.0.target().to_owned().into() } } impl From for XPathWrapper { fn from(value: T) -> Self { Self(value) } } impl XPathWrapper { pub(crate) fn into_inner(self) -> T { self.0 } } pub(crate) fn parse_expression( expression: &str, resolver: Option>, is_in_html_document: bool, ) -> Fallible { xpath::parse(expression, resolver.map(XPathWrapper), is_in_html_document).map_err(|error| { match error { xpath::ParserError::FailedToResolveNamespacePrefix => Error::Namespace(None), _ => Error::Syntax(Some(format!("Failed to parse XPath expression: {error:?}"))), } }) } enum PrecedingNodeIteratorWithoutAncestors { Done, NotDone { current: DomRoot, /// When we're currently walking over the subtree of a node in reverse tree order /// then this is the iterator for doing that. subtree_iterator: Option, }, } /// Returns the previous element (in tree order) that is not an ancestor of `node`. fn previous_non_ancestor_node(node: &Node) -> Option> { let mut current = DomRoot::from_ref(node); loop { if let Some(previous_sibling) = current.GetPreviousSibling() { return Some(previous_sibling); } current = current.GetParentNode()?; } } impl PrecedingNodeIteratorWithoutAncestors { fn new(node: &Node) -> Self { let Some(current) = previous_non_ancestor_node(node) else { return Self::Done; }; Self::NotDone { subtree_iterator: current .descending_last_children() .last() .map(|node| node.preceding_nodes(¤t)), current, } } } impl Iterator for PrecedingNodeIteratorWithoutAncestors { type Item = DomRoot; fn next(&mut self) -> Option { let Self::NotDone { current, subtree_iterator, } = self else { return None; }; if let Some(next_node) = subtree_iterator .as_mut() .and_then(|iterator| iterator.next()) { return Some(next_node); } // Our current subtree is exhausted. Return the root of the subtree and move on to the next one // in inverse tree order. let Some(next_subtree) = previous_non_ancestor_node(current) else { *self = Self::Done; return None; }; *current = next_subtree; *subtree_iterator = current .descending_last_children() .last() .map(|node| node.preceding_nodes(current)); self.next() } }