diff --git a/Cargo.toml b/Cargo.toml index 3d4bbaf..33cc120 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nipper" -version = "0.1.9" +version = "0.1.10" description = "HTML manipulation with CSS seletors" license = "MIT/Apache-2.0" repository = "https://github.com/importcjj/nipper" @@ -13,16 +13,18 @@ readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -html5ever = "0.25.1" -selectors = "0.22.0" -cssparser = "0.27.2" +html5ever = "0.26.0" +selectors = "0.25.0" +cssparser = "0.31.2" tendril = "0.4.2" -markup5ever = "0.10.0" +markup5ever = "0.11.0" +indexmap = "2.1.0" +fxhash = "0.2.1" [dev-dependencies] reqwest = { version = "0.11.3", features = ["blocking"] } regex = "1.4.5" lazy_static = "1.4.0" -readability = "0.2.0" +readability = "0.3.0" url = "2.2.1" diff --git a/src/css.rs b/src/css.rs new file mode 100644 index 0000000..8b1fa67 --- /dev/null +++ b/src/css.rs @@ -0,0 +1,65 @@ +use std::convert::AsRef; +use std::fmt; +use std::ops::Deref; + +use cssparser::{self, ToCss}; +use html5ever::LocalName; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct CssString(String); + +impl Deref for CssString { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef for CssString { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl From<&str> for CssString { + fn from(value: &str) -> Self { + CssString(value.to_owned()) + } +} + +impl ToCss for CssString { + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + //dest.write_str(&self.0) + cssparser::serialize_string(&self.0, dest) + } +} + +#[derive(Clone, Eq, PartialEq, Debug, Default)] +pub struct CssLocalName(LocalName); + +impl ToCss for CssLocalName { + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + dest.write_str(&self.0) + } +} + +impl From<&str> for CssLocalName { + fn from(value: &str) -> Self { + CssLocalName(value.into()) + } +} + +impl Deref for CssLocalName { + type Target = LocalName; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/src/document.rs b/src/document.rs index cdd34ea..c2103d8 100644 --- a/src/document.rs +++ b/src/document.rs @@ -92,7 +92,7 @@ impl TreeSink for Document { NodeData::Element(Element { template_contents: Some(ref contents), .. - }) => contents.clone(), + }) => *contents, _ => panic!("not a template element!"), }) } @@ -151,7 +151,7 @@ impl TreeSink for Document { // Create a Processing Instruction node. fn create_pi(&mut self, target: StrTendril, data: StrTendril) -> NodeId { self.tree.create_node(NodeData::ProcessingInstruction { - target: target, + target, contents: data, }) } @@ -243,9 +243,9 @@ impl TreeSink for Document { self.tree.append_child_data_of( &root, NodeData::Doctype { - name: name, - public_id: public_id, - system_id: system_id, + name, + public_id, + system_id, }, ); } diff --git a/src/dom_tree.rs b/src/dom_tree.rs index 9969053..8baee95 100644 --- a/src/dom_tree.rs +++ b/src/dom_tree.rs @@ -265,7 +265,7 @@ impl Tree { with_cell_mut!(self.nodes, nodes, { let mut new_nodes = tree.nodes.into_inner(); assert!( - new_nodes.len() > 0, + !new_nodes.is_empty(), "The tree should have at leaset one root node" ); assert!( @@ -296,7 +296,7 @@ impl Tree { let last_child_id = fix_id!(root.last_child); // Update new parent's first and last child id. - let mut parent = get_node_unchecked_mut!(nodes, id); + let parent = get_node_unchecked_mut!(nodes, id); if parent.first_child.is_none() { parent.first_child = first_child_id; } @@ -306,7 +306,7 @@ impl Tree { // Update next_sibling_id if let Some(last_child_id) = parent_last_child_id { - let mut last_child = get_node_unchecked_mut!(nodes, last_child_id); + let last_child = get_node_unchecked_mut!(nodes, last_child_id); last_child.next_sibling = first_child_id; } @@ -317,7 +317,7 @@ impl Tree { node.parent = node.parent.and_then(|parent_id| match parent_id.value { i if i < TRUE_ROOT_ID => None, i if i == TRUE_ROOT_ID => Some(*id), - i @ _ => fix_id!(Some(NodeId::new(i))), + i => fix_id!(Some(NodeId::new(i))), }); // Update prev_sibling_id @@ -343,7 +343,7 @@ impl Tree { with_cell_mut!(self.nodes, nodes, { let mut new_nodes = tree.nodes.into_inner(); assert!( - new_nodes.len() > 0, + !new_nodes.is_empty(), "The tree should have at leaset one root node" ); assert!( @@ -371,7 +371,7 @@ impl Tree { let first_child_id = fix_id!(root.first_child); let last_child_id = fix_id!(root.last_child); - let mut node = get_node_unchecked_mut!(nodes, id); + let node = get_node_unchecked_mut!(nodes, id); let prev_sibling_id = node.prev_sibling; let parent_id = node.parent; @@ -380,25 +380,24 @@ impl Tree { // Update prev sibling's next sibling if let Some(prev_sibling_id) = prev_sibling_id { - let mut prev_sibling = get_node_unchecked_mut!(nodes, prev_sibling_id); + let prev_sibling = get_node_unchecked_mut!(nodes, prev_sibling_id); prev_sibling.next_sibling = first_child_id; // Update parent's first child. } else if let Some(parent_id) = parent_id { - let mut parent = get_node_unchecked_mut!(nodes, parent_id); + let parent = get_node_unchecked_mut!(nodes, parent_id); parent.first_child = first_child_id; } - let mut i = 0; let mut last_valid_child = 0; let mut first_valid_child = true; // Fix nodes's ref id. - for node in new_nodes.iter_mut() { + for (i, node) in new_nodes.iter_mut().enumerate() { node.parent = node .parent .and_then(|old_parent_id| match old_parent_id.value { i if i < TRUE_ROOT_ID => None, i if i == TRUE_ROOT_ID => parent_id, - i @ _ => fix_id!(Some(NodeId::new(i))), + i => fix_id!(Some(NodeId::new(i))), }); // Update first child's prev_sibling @@ -416,11 +415,10 @@ impl Tree { node.last_child = fix_id!(node.last_child); node.prev_sibling = fix_id!(node.prev_sibling); node.next_sibling = fix_id!(node.next_sibling); - i += 1; } // Update last child's next_sibling. - new_nodes[last_valid_child as usize].next_sibling = Some(*id); + new_nodes[last_valid_child].next_sibling = Some(*id); // Put all the new nodes except the root node into the nodes. nodes.extend(new_nodes); @@ -501,7 +499,7 @@ impl Tree { node.last_child = None; if let Some(new_parent_id) = new_parent_id { - let mut new_parent = get_node_unchecked_mut!(nodes, new_parent_id); + let new_parent = get_node_unchecked_mut!(nodes, new_parent_id); new_parent.first_child = first_child_id; new_parent.last_child = last_child_id; } @@ -557,9 +555,8 @@ impl Tree { let node_a = unsafe { nodes.get_unchecked(a.value) }; let node_b = unsafe { nodes.get_unchecked(b.value) }; - let r = f(node_a, node_b); // self.nodes.set(nodes); - r + f(node_a, node_b) } } @@ -603,36 +600,27 @@ impl Debug for InnerNode { impl InnerNode { pub fn is_document(&self) -> bool { - match self.data { - NodeData::Document => true, - _ => false, - } + matches!(self.data, NodeData::Document) } pub fn is_element(&self) -> bool { - match self.data { - NodeData::Element(_) => true, - _ => false, - } + matches!(self.data, NodeData::Element(_)) } pub fn is_text(&self) -> bool { - match self.data { - NodeData::Text { .. } => true, - _ => false, - } + matches!(self.data, NodeData::Text { .. }) } } impl Clone for InnerNode { fn clone(&self) -> Self { Self { - id: self.id.clone(), - parent: self.parent.clone(), - prev_sibling: self.prev_sibling.clone(), - next_sibling: self.next_sibling.clone(), - first_child: self.first_child.clone(), - last_child: self.last_child.clone(), + id: self.id, + parent: self.parent, + prev_sibling: self.prev_sibling, + next_sibling: self.next_sibling, + first_child: self.first_child, + last_child: self.last_child, data: self.data.clone(), } } @@ -775,18 +763,18 @@ impl<'a> Node<'a> { } pub fn add_class(&self, class: &str) { - if class.trim().len() == 0 { + if class.trim().is_empty() { return; } - self.update(|node| match node.data { - NodeData::Element(ref mut e) => { + self.update(|node| { + if let NodeData::Element(ref mut e) = node.data { let mut attr = e.attrs.iter_mut().find(|attr| &attr.name.local == "class"); let set: HashSet = class - .split(" ") + .split(' ') .map(|s| s.trim()) - .filter(|s| s.len() > 0) + .filter(|s| !s.is_empty()) .map(|s| s.to_string()) .collect(); @@ -807,29 +795,28 @@ impl<'a> Node<'a> { e.attrs.push(Attribute { name, value }) } } - _ => (), }) } pub fn remove_class(&self, class: &str) { - if class.trim().len() == 0 { + if class.trim().is_empty() { return; } - self.update(|node| match node.data { - NodeData::Element(ref mut e) => { + self.update(|node| { + if let NodeData::Element(ref mut e) = node.data { e.attrs .iter_mut() .find(|attr| &attr.name.local == "class") .map(|attr| { let mut set: HashSet<&str> = attr .value - .split(" ") + .split(' ') .map(|s| s.trim()) - .filter(|s| s.len() > 0) + .filter(|s| !s.is_empty()) .collect(); - let removes = class.split(" ").map(|s| s.trim()).filter(|s| s.len() > 0); + let removes = class.split(' ').map(|s| s.trim()).filter(|s| !s.is_empty()); for remove in removes { set.remove(remove); @@ -839,7 +826,6 @@ impl<'a> Node<'a> { StrTendril::from(set.into_iter().collect::>().join(" ")); }); } - _ => (), }) } @@ -856,7 +842,7 @@ impl<'a> Node<'a> { pub fn attrs(&self) -> Vec { self.query(|node| match node.data { - NodeData::Element(ref e) => e.attrs.iter().map(|attr| attr.clone()).collect(), + NodeData::Element(ref e) => e.attrs.to_vec(), _ => vec![], }) } @@ -939,7 +925,7 @@ impl<'a> Node<'a> { } } - NodeData::Text { ref contents } => text.push_tendril(&contents), + NodeData::Text { ref contents } => text.push_tendril(contents), _ => continue, } @@ -1040,7 +1026,7 @@ impl<'a> Serialize for SerializableNodeRef<'a> { IncludeNode => vec![SerializeOp::Open(id)], ChildrenOnly(_) => children_of!(nodes, id) .into_iter() - .map(|h| SerializeOp::Open(h)) + .map(SerializeOp::Open) .collect(), }; @@ -1061,9 +1047,9 @@ impl<'a> Serialize for SerializableNodeRef<'a> { Ok(()) } - NodeData::Doctype { ref name, .. } => serializer.write_doctype(&name), - NodeData::Text { ref contents } => serializer.write_text(&contents), - NodeData::Comment { ref contents } => serializer.write_comment(&contents), + NodeData::Doctype { ref name, .. } => serializer.write_doctype(name), + NodeData::Text { ref contents } => serializer.write_text(contents), + NodeData::Comment { ref contents } => serializer.write_comment(contents), NodeData::ProcessingInstruction { ref target, ref contents, diff --git a/src/element.rs b/src/element.rs index 2390457..5f9c1e7 100644 --- a/src/element.rs +++ b/src/element.rs @@ -1,14 +1,17 @@ -use crate::dom_tree::{Node, NodeData}; -use crate::matcher::InnerSelector; +use crate::css::CssLocalName; +use crate::dom_tree::{Node, NodeData, NodeRef}; +use crate::matcher::{InnerSelector, NonTSPseudoClass}; + +use std::ops::Deref; + use markup5ever::{namespace_url, ns}; use selectors::attr::AttrSelectorOperation; use selectors::attr::CaseSensitivity; use selectors::attr::NamespaceConstraint; use selectors::context::MatchingContext; -use selectors::matching::ElementSelectorFlags; +use selectors::matching::{matches_selector_list, ElementSelectorFlags}; use selectors::parser::SelectorImpl; -use selectors::OpaqueElement; -use std::ops::Deref; +use selectors::{OpaqueElement, SelectorList}; impl<'a> selectors::Element for Node<'a> { type Impl = InnerSelector; @@ -60,7 +63,7 @@ impl<'a> selectors::Element for Node<'a> { fn has_local_name(&self, local_name: &::BorrowedLocalName) -> bool { self.query(|node| { if let NodeData::Element(ref e) = node.data { - return &e.name.local == local_name; + return &e.name.local == local_name.deref(); } false @@ -102,7 +105,7 @@ impl<'a> selectors::Element for Node<'a> { if let NodeData::Element(ref e) = node.data { return e.attrs.iter().any(|attr| match *ns { NamespaceConstraint::Specific(url) if *url != attr.name.ns => false, - _ => *local_name == attr.name.local && operation.eval_str(&attr.value), + _ => *local_name.as_ref() == attr.name.local && operation.eval_str(&attr.value), }); } @@ -110,16 +113,30 @@ impl<'a> selectors::Element for Node<'a> { }) } - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, - _pc: &::NonTSPseudoClass, - _context: &mut MatchingContext, - _flags_setter: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { - false + pseudo: &::NonTSPseudoClass, + context: &mut MatchingContext, + ) -> bool { + use self::NonTSPseudoClass::*; + match pseudo { + Active | Focus | Hover | Enabled | Disabled | Checked | Indeterminate | Visited => { + false + } + AnyLink | Link => match self.node_name() { + Some(node_name) => { + matches!(node_name.deref(), "a" | "area" | "link") + && self.attr("href").is_some() + } + None => false, + }, + Has(list) => { + //it checks only in self, not in inlines! + has_descendant_match(self, list, context) + + //true + } + } } fn match_pseudo_element( @@ -165,7 +182,7 @@ impl<'a> selectors::Element for Node<'a> { fn has_class( &self, - name: &::ClassName, + name: &::LocalName, case_sensitivity: CaseSensitivity, ) -> bool { self.query(|node| { @@ -183,23 +200,12 @@ impl<'a> selectors::Element for Node<'a> { }) } - // Returns the mapping from the `exportparts` attribute in the regular direction, that is, inner-tree->outer-tree. - fn exported_part( - &self, - _name: &::PartName, - ) -> Option<::PartName> { - None - } - // Returns the mapping from the `exportparts` attribute in the regular direction, that is, outer-tree->inner-tree. - fn imported_part( - &self, - _name: &::PartName, - ) -> Option<::PartName> { + fn imported_part(&self, _name: &CssLocalName) -> Option { None } - fn is_part(&self, _name: &::PartName) -> bool { + fn is_part(&self, _name: &CssLocalName) -> bool { false } @@ -215,4 +221,30 @@ impl<'a> selectors::Element for Node<'a> { fn is_root(&self) -> bool { self.is_document() } + + fn first_element_child(&self) -> Option { + self.children() + .iter() + .find(|&child| child.is_element()) + .cloned() + } + + fn apply_selector_flags(&self, _flags: ElementSelectorFlags) {} +} + +fn has_descendant_match( + n: &NodeRef, + selectors_list: &SelectorList, + ctx: &mut MatchingContext, +) -> bool { + let mut node = n.first_child(); + while let Some(ref n) = node { + if matches_selector_list(selectors_list, n, ctx) + || (n.is_element() && has_descendant_match(n, selectors_list, ctx)) + { + return true; + } + node = n.next_sibling(); + } + false } diff --git a/src/lib.rs b/src/lib.rs index 258fd5e..c35cc25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,9 @@ //! // #![deny(missing_docs)] // TODO: add this back in. +extern crate html5ever; + +mod css; mod document; mod dom_tree; mod element; diff --git a/src/manipulation.rs b/src/manipulation.rs index 02ed3ad..d709f50 100644 --- a/src/manipulation.rs +++ b/src/manipulation.rs @@ -62,16 +62,14 @@ impl<'a> Selection<'a> { T: Into, { let dom = parse_html!(html); - let mut i = 0; - for node in self.nodes() { + for (i, node) in self.nodes().iter().enumerate() { if i + 1 == self.size() { node.append_prev_siblings_from_another_tree(dom.tree); break; } else { node.append_prev_siblings_from_another_tree(dom.tree.clone()); } - i += 1; } self.remove() @@ -97,16 +95,14 @@ impl<'a> Selection<'a> { T: Into, { let dom = parse_html!(html); - let mut i = 0; - for node in self.nodes() { + for (i, node) in self.nodes().iter().enumerate() { if i + 1 == self.size() { node.append_children_from_another_tree(dom.tree); break; } else { node.append_children_from_another_tree(dom.tree.clone()); } - i += 1; } } diff --git a/src/matcher.rs b/src/matcher.rs index d3b3f34..2168b8d 100644 --- a/src/matcher.rs +++ b/src/matcher.rs @@ -1,13 +1,20 @@ +use crate::css::{CssLocalName, CssString}; use crate::dom_tree::{NodeData, NodeId, NodeRef}; + use cssparser::ParseError; -use html5ever::{LocalName, Namespace}; -use selectors::matching; +use cssparser::{self, CowRcStr, SourceLocation, ToCss}; +use html5ever::Namespace; use selectors::parser::{self, SelectorList, SelectorParseErrorKind}; use selectors::visitor; use selectors::Element; -use std::collections::HashSet; +use selectors::{matching, NthIndexCache}; use std::fmt; +use fxhash::FxBuildHasher; +use indexmap::IndexSet; + +pub type NodeIdSet = IndexSet; + /// CSS selector. #[derive(Clone, Debug)] pub struct Matcher { @@ -19,19 +26,26 @@ impl Matcher { pub fn new(sel: &str) -> Result> { let mut input = cssparser::ParserInput::new(sel); let mut parser = cssparser::Parser::new(&mut input); - selectors::parser::SelectorList::parse(&InnerSelectorParser, &mut parser) - .map(|selector_list| Matcher { selector_list }) + selectors::parser::SelectorList::parse( + &InnerSelectorParser, + &mut parser, + parser::ParseRelative::ForNesting, + ) + .map(|selector_list| Matcher { selector_list }) } pub(crate) fn match_element(&self, element: &E) -> bool where E: Element, { + let mut nth_cache = NthIndexCache::default(); let mut ctx = matching::MatchingContext::new( matching::MatchingMode::Normal, None, - None, + &mut nth_cache, matching::QuirksMode::NoQuirks, + matching::NeedsSelectorFlags::No, + matching::IgnoreNthChildForInvalidation::No, ); matching::matches_selector_list(&self.selector_list, element, &mut ctx) @@ -43,7 +57,7 @@ pub struct Matches { roots: Vec, nodes: Vec, matcher: Matcher, - set: HashSet, + set: NodeIdSet, match_scope: MatchScope, } @@ -59,8 +73,8 @@ impl Matches { Self { roots: vec![node], nodes: vec![], - matcher: matcher, - set: HashSet::new(), + matcher, + set: NodeIdSet::default(), match_scope, } } @@ -73,8 +87,8 @@ impl Matches { Self { roots: nodes.collect(), nodes: vec![], - matcher: matcher, - set: HashSet::new(), + matcher, + set: NodeIdSet::default(), match_scope, } } @@ -127,29 +141,120 @@ pub(crate) struct InnerSelectorParser; impl<'i> parser::Parser<'i> for InnerSelectorParser { type Impl = InnerSelector; type Error = parser::SelectorParseErrorKind<'i>; + + fn parse_non_ts_pseudo_class( + &self, + location: SourceLocation, + name: CowRcStr<'i>, + ) -> Result> { + use self::NonTSPseudoClass::*; + if name.eq_ignore_ascii_case("any-link") { + Ok(AnyLink) + } else if name.eq_ignore_ascii_case("link") { + Ok(Link) + } else if name.eq_ignore_ascii_case("visited") { + Ok(Visited) + } else if name.eq_ignore_ascii_case("active") { + Ok(Active) + } else if name.eq_ignore_ascii_case("focus") { + Ok(Focus) + } else if name.eq_ignore_ascii_case("hover") { + Ok(Hover) + } else if name.eq_ignore_ascii_case("enabled") { + Ok(Enabled) + } else if name.eq_ignore_ascii_case("disabled") { + Ok(Disabled) + } else if name.eq_ignore_ascii_case("checked") { + Ok(Checked) + } else if name.eq_ignore_ascii_case("indeterminate") { + Ok(Indeterminate) + } else { + Err( + location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( + name, + )), + ) + } + } + fn parse_non_ts_functional_pseudo_class<'t>( + &self, + name: CowRcStr<'i>, + arguments: &mut cssparser::Parser<'i, 't>, + ) -> Result> { + if name.starts_with("has") { + let list: SelectorList = + SelectorList::parse(self, arguments, parser::ParseRelative::No)?; + Ok(NonTSPseudoClass::Has(list)) + } else { + Err(arguments.new_custom_error( + SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name), + )) + } + } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct InnerSelector; impl parser::SelectorImpl for InnerSelector { - type ExtraMatchingData = String; - type AttrValue = String; - type Identifier = LocalName; - type ClassName = LocalName; - type PartName = LocalName; - type LocalName = LocalName; + type ExtraMatchingData<'a> = (); + type AttrValue = CssString; + type Identifier = CssLocalName; + type LocalName = CssLocalName; type NamespaceUrl = Namespace; - type NamespacePrefix = LocalName; - type BorrowedLocalName = LocalName; + type NamespacePrefix = CssLocalName; + type BorrowedLocalName = CssLocalName; type BorrowedNamespaceUrl = Namespace; type NonTSPseudoClass = NonTSPseudoClass; type PseudoElement = PseudoElement; } -#[derive(Clone, Eq, PartialEq)] -pub struct NonTSPseudoClass; +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum NonTSPseudoClass { + AnyLink, + Link, + Visited, + Active, + Focus, + Hover, + Enabled, + Disabled, + Checked, + Indeterminate, + Has(SelectorList), +} + +impl ToCss for NonTSPseudoClass { + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + match self { + NonTSPseudoClass::AnyLink => dest.write_str(":any-link"), + NonTSPseudoClass::Link => dest.write_str(":link"), + NonTSPseudoClass::Visited => dest.write_str(":visited"), + NonTSPseudoClass::Active => dest.write_str(":active"), + NonTSPseudoClass::Focus => dest.write_str(":focus"), + NonTSPseudoClass::Hover => dest.write_str(":hover"), + NonTSPseudoClass::Enabled => dest.write_str(":enabled"), + NonTSPseudoClass::Disabled => dest.write_str(":disabled"), + NonTSPseudoClass::Checked => dest.write_str(":checked"), + NonTSPseudoClass::Indeterminate => dest.write_str(":indeterminate"), + NonTSPseudoClass::Has(list) => { + dest.write_str("has:(")?; + list.to_css(dest)?; + dest.write_str(")") + } + } + } + + fn to_css_string(&self) -> String { + let mut s = String::new(); + self.to_css(&mut s).unwrap(); + s + } +} impl parser::NonTSPseudoClass for NonTSPseudoClass { type Impl = InnerSelector; @@ -162,14 +267,6 @@ impl parser::NonTSPseudoClass for NonTSPseudoClass { false } - fn has_zero_specificity(&self) -> bool { - false - } -} - -impl parser::Visit for NonTSPseudoClass { - type Impl = InnerSelector; - fn visit(&self, _visitor: &mut V) -> bool where V: visitor::SelectorVisitor, @@ -178,7 +275,10 @@ impl parser::Visit for NonTSPseudoClass { } } -impl cssparser::ToCss for NonTSPseudoClass { +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct PseudoElement; + +impl ToCss for PseudoElement { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, @@ -187,18 +287,14 @@ impl cssparser::ToCss for NonTSPseudoClass { } } -#[derive(Clone, Eq, PartialEq)] -pub struct PseudoElement; - impl parser::PseudoElement for PseudoElement { type Impl = InnerSelector; -} -impl cssparser::ToCss for PseudoElement { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - dest.write_str("") + fn accepts_state_pseudo_classes(&self) -> bool { + false + } + + fn valid_after_slotted(&self) -> bool { + false } } diff --git a/src/query.rs b/src/query.rs index 46d5f91..3dff84d 100644 --- a/src/query.rs +++ b/src/query.rs @@ -21,7 +21,7 @@ impl<'a> Selection<'a> { if self.length() > 0 { return self .nodes() - .into_iter() + .iter() .filter(|node| matcher.match_element(*node)) .count() > 0; diff --git a/src/selection.rs b/src/selection.rs index f533326..f70083b 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -3,17 +3,11 @@ use crate::dom_tree::Node; /// Selection represents a collection of nodes matching some criteria. The /// initial Selection object can be created by using [`Document::select`], and then /// manipulated using methods itself. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct Selection<'a> { pub(crate) nodes: Vec>, } -impl<'a> Default for Selection<'a> { - fn default() -> Self { - Self { nodes: vec![] } - } -} - impl<'a> From> for Selection<'a> { fn from(node: Node<'a>) -> Selection { Self { nodes: vec![node] } diff --git a/src/traversal.rs b/src/traversal.rs index 7dbccff..abdc05c 100644 --- a/src/traversal.rs +++ b/src/traversal.rs @@ -16,7 +16,7 @@ impl Document { let matcher = Matcher::new(sel).expect("Invalid CSS selector"); let root = self.tree.root(); Selection { - nodes: Matches::from_one(root, matcher.clone(), MatchScope::IncludeNode).collect(), + nodes: Matches::from_one(root, matcher, MatchScope::IncludeNode).collect(), } } @@ -37,8 +37,8 @@ impl Document { Ok(matcher) => { let root = self.tree.root(); let nodes: Vec = - Matches::from_one(root, matcher.clone(), MatchScope::ChildrenOnly).collect(); - if nodes.len() > 0 { + Matches::from_one(root, matcher, MatchScope::ChildrenOnly).collect(); + if !nodes.is_empty() { Some(Selection { nodes }) } else { None @@ -101,7 +101,7 @@ impl<'a> Selection<'a> { MatchScope::ChildrenOnly, ) .collect(); - if nodes.len() > 0 { + if !nodes.is_empty() { Some(Selection { nodes }) } else { None diff --git a/tests/pseudo-class.rs b/tests/pseudo-class.rs new file mode 100644 index 0000000..27841d6 --- /dev/null +++ b/tests/pseudo-class.rs @@ -0,0 +1,33 @@ +use nipper::Document; + +#[test] +fn test_pseudo_class_has() { + let html = r#" +
+ One + Two + Three +
"#; + let document = Document::from(html); + let sel = r#"div:has(a[href="/1"]) a span"#; + let span = document.select(sel); + + let text: &str = &span.text(); + assert!(text == "Three"); +} + +#[test] +fn test_pseudo_class_has_any_link() { + let html = r#" +
+ One + Two + Three +
"#; + let document = Document::from(html); + let sel = r#"div:has(*:any-link) a span"#; + let span = document.select(sel); + + let text: &str = &span.text(); + assert!(text == "Three"); +}