From 191f47e83a7dec4badd82e6952372cb2f5375343 Mon Sep 17 00:00:00 2001 From: Mykola Humanov Date: Mon, 28 Oct 2024 18:08:32 +0200 Subject: [PATCH] src/selection.rs: implement `Selection::filter_selection`; cargo fmt --- CHANGELOG.md | 1 + src/document.rs | 4 ++- src/dom_tree.rs | 11 ++++---- src/entities.rs | 1 - src/lib.rs | 2 +- src/selection.rs | 59 ++++++++++++++++++++++++++-------------- tests/data.rs | 2 +- tests/parsing.rs | 2 -- tests/selection-query.rs | 37 +++++++++++++++++++++---- 9 files changed, 83 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 546bf55..404a7e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the `dom_query` crate will be documented in this file. ### Added - Added `Selection::filter` , `Selection::filter_matcher` and `Selection::try_filter` methods that filter a current selection. +- Added `Selection::filter_selection` method that filters a current selection with another selection. ## [0.7.0] - 2024-10-27 diff --git a/src/document.rs b/src/document.rs index ab099c1..3bb96ea 100644 --- a/src/document.rs +++ b/src/document.rs @@ -230,7 +230,9 @@ impl TreeSink for Document { /// Should never be called on a non-element node; Feel free to `panic!`. #[inline] fn elem_name(&self, target: &Self::Handle) -> Self::ElemName<'_> { - self.tree.get_name(target).expect("target node is not an element!") + self.tree + .get_name(target) + .expect("target node is not an element!") } /// Create an element. diff --git a/src/dom_tree.rs b/src/dom_tree.rs index 62124cc..0c0543b 100644 --- a/src/dom_tree.rs +++ b/src/dom_tree.rs @@ -51,16 +51,17 @@ impl Tree { NodeRef { id, tree: self } } - /// Gets node's name by by id - pub fn get_name<'a>(&'a self, id: &NodeId) -> Option> { + /// Gets node's name by by id + pub fn get_name<'a>(&'a self, id: &NodeId) -> Option> { Ref::filter_map(self.nodes.borrow(), |nodes| { let node = nodes.get(id.value)?; if let NodeData::Element(ref el) = node.data { Some(&el.name) - }else{ - None + } else { + None } - }).ok() + }) + .ok() } } diff --git a/src/entities.rs b/src/entities.rs index 66e8067..f214755 100644 --- a/src/entities.rs +++ b/src/entities.rs @@ -1,4 +1,3 @@ - #[cfg(feature = "hashbrown")] mod inline { use hashbrown::HashSet; diff --git a/src/lib.rs b/src/lib.rs index 7061b44..b1f8947 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -315,7 +315,7 @@ //! assert_eq!(text.to_string(), "Test Page"); //! //! ``` -//! +//! //! ## Accessing immediate text //! //! ``` diff --git a/src/selection.rs b/src/selection.rs index e844fb2..9b66c2a 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -203,25 +203,25 @@ impl<'a> Selection<'a> { /// Checks the current matches set of elements against a selection and /// returns true if at least one of these elements matches. - pub fn is_selection(&self, sel: &Selection) -> bool { - if self.is_empty() || sel.is_empty() { + pub fn is_selection(&self, other: &Selection) -> bool { + if self.is_empty() || other.is_empty() { return false; } - let m: Vec = sel.nodes().iter().map(|node| node.id.value).collect(); + let m: Vec = other.nodes().iter().map(|node| node.id.value).collect(); self.nodes().iter().any(|node| m.contains(&node.id.value)) } /// Filters the current set of matched elements to those that match the /// given CSS selector. - /// + /// /// # Panics - /// + /// /// # Arguments - /// + /// /// * `sel` - The CSS selector to match against. - /// + /// /// # Returns - /// + /// /// A new Selection object containing the matched elements. pub fn filter(&self, sel: &str) -> Selection<'a> { if self.is_empty() { @@ -231,15 +231,15 @@ impl<'a> Selection<'a> { self.filter_matcher(&matcher) } - /// Filters the current set of matched elements to those that match the + /// Reduces the current set of matched elements to those that match the /// given CSS selector. - /// + /// /// # Arguments - /// + /// /// * `sel` - The CSS selector to match against. - /// + /// /// # Returns - /// + /// /// `None` if the selector was invalid, otherwise a new `Selection` object containing the matched elements. pub fn try_filter(&self, sel: &str) -> Option> { if self.is_empty() { @@ -248,25 +248,44 @@ impl<'a> Selection<'a> { Matcher::new(sel).ok().map(|m| self.filter_matcher(&m)) } - /// Filters the current set of matched elements to those that match the + /// Reduces the current set of matched elements to those that match the /// given matcher. - /// + /// /// # Arguments - /// + /// /// * `matcher` - The matcher to match against. - /// + /// /// # Returns - /// + /// /// A new Selection object containing the matched elements. pub fn filter_matcher(&self, matcher: &Matcher) -> Selection<'a> { if self.is_empty() { return self.clone(); } - let nodes = self.nodes().iter() - .filter(|&node| matcher.match_element(node)).cloned().collect(); + let nodes = self + .nodes() + .iter() + .filter(|&node| matcher.match_element(node)) + .cloned() + .collect(); Selection { nodes } } + /// Reduces the set of matched elements to those that match a node in the specified `Selection`. + /// It returns a new `Selection` for this subset of elements. + pub fn filter_selection(&self, other: &Selection) -> Selection<'a> { + if self.is_empty() || other.is_empty() { + return self.clone(); + } + let m: Vec = other.nodes().iter().map(|node| node.id.value).collect(); + let nodes = self + .nodes() + .iter() + .filter(|&node| m.contains(&node.id.value)) + .cloned() + .collect(); + Selection { nodes } + } } //manipulating methods diff --git a/tests/data.rs b/tests/data.rs index ad3113b..2fa78ec 100644 --- a/tests/data.rs +++ b/tests/data.rs @@ -53,4 +53,4 @@ pub static HEADING_CONTENTS: &str = r#"

This is a test page contents.

- "#; \ No newline at end of file + "#; diff --git a/tests/parsing.rs b/tests/parsing.rs index 012313c..8008e34 100644 --- a/tests/parsing.rs +++ b/tests/parsing.rs @@ -9,8 +9,6 @@ mod alloc; mod data; use data::HEADING_CONTENTS; - - #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn parse_doc_str() { diff --git a/tests/selection-query.rs b/tests/selection-query.rs index 660b09a..f60696e 100644 --- a/tests/selection-query.rs +++ b/tests/selection-query.rs @@ -8,8 +8,6 @@ use wasm_bindgen_test::*; mod alloc; - - #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_is() { @@ -56,7 +54,6 @@ fn test_is_selection_not() { ); } - #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_filter_selection() { @@ -74,7 +71,6 @@ fn test_filter_selection() { assert!(sel.select("h1").exists()); } - #[cfg_attr(not(target_arch = "wasm32"), test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_try_filter_selection() { @@ -85,4 +81,35 @@ fn test_try_filter_selection() { let filtered_sel = sel.try_filter("div.text-content").unwrap(); assert!(!filtered_sel.select("h1").exists()); assert!(sel.select("h1").exists()); -} \ No newline at end of file +} + +#[cfg_attr(not(target_arch = "wasm32"), test)] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn test_filter_selection_other() { + let doc: Document = r#" + + TEST + +
+

Content text has a link

+
+ + + + "# + .into(); + + // selecting all links in the document + let sel_with_links = doc.select("a[href]"); + + assert_eq!(sel_with_links.length(), 2); + // selecting every element inside `.content` + let content_sel = doc.select("div.content *"); + + // filter selection by content selection, so now we get only links (actually only 1 link) that are inside `.content` + let filtered_sel = sel_with_links.filter_selection(&content_sel); + + assert_eq!(filtered_sel.length(), 1); +}