Skip to content

Commit

Permalink
Merge pull request #26 from niklak/fix/selection-replace-selection
Browse files Browse the repository at this point in the history
Fixed `Selection::append_selection` and `Selection::replace_with_selection`
  • Loading branch information
niklak authored Nov 5, 2024
2 parents 3b76f4c + 92fdcb6 commit 45b44fc
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 46 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ All notable changes to the `dom_query` crate will be documented in this file.
- Added `NodeRef::prepend_html` method, that parses html string and inserts its parsed nodes at the beginning of the node content.
- Added `Selection::prepend_html` method, which parses an HTML string and inserts its parsed nodes at the beginning of the content of all matched nodes.

### Fixed
- Fixed `Selection::append_selection` to work with selections with multiple nodes and selections from another tree.
- Fixed `Selection::replace_with_selection` to work with selections with multiple nodes and selections from another tree.


## [0.8.0] - 2024-11-03

### Changed
Expand Down
3 changes: 1 addition & 2 deletions Examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,6 @@ assert!(doc.select(r#"#main #second:has-text("test")"#).exists());

main_node.append_html(r#"<p id="third">Wonderful</p>"#);
assert_eq!(doc.select("#main #third").text().as_ref(), "Wonderful");
dbg!(doc.html());
// There is also a `prepend_child` and `prepend_html` methods which allows
// to insert content to the begging of the node.
main_node.prepend_html(r#"<p id="minus-one">-1</p><p id="zero">0</p>"#);
Expand Down Expand Up @@ -612,7 +611,7 @@ sel.rename("p");

// after renaming, there are no `div` and `span` elements
assert_eq!(doc.select("div.content > div, div.content > span").length(), 0);
// but there are three `p` elements
// but there are four `p` elements
assert_eq!(doc.select("div.content > p").length(), 4);
```
</details>
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,6 @@ assert!(doc.select(r#"#main #second:has-text("test")"#).exists());

main_node.append_html(r#"<p id="third">Wonderful</p>"#);
assert_eq!(doc.select("#main #third").text().as_ref(), "Wonderful");
dbg!(doc.html());
// There is also a `prepend_child` and `prepend_html` methods which allows
// to insert content to the begging of the node.
main_node.prepend_html(r#"<p id="minus-one">-1</p><p id="zero">0</p>"#);
Expand Down Expand Up @@ -643,7 +642,7 @@ sel.rename("p");

// after renaming, there are no `div` and `span` elements
assert_eq!(doc.select("div.content > div, div.content > span").length(), 0);
// but there are three `p` elements
// but there are four `p` elements
assert_eq!(doc.select("div.content > p").length(), 4);
```
</details>
Expand Down
7 changes: 5 additions & 2 deletions src/dom_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,9 +504,12 @@ impl Tree {
NodeId::new(self.nodes.borrow().len())
}

/// Adds nodes from another tree to the current tree and
/// Adds nodes from another tree to the current tree and
/// then applies a function to the first merged node
pub(crate) fn merge_with_fn<F>(&self, other: Tree, f: F) where F: FnOnce(NodeId) {
pub(crate) fn merge_with_fn<F>(&self, other: Tree, f: F)
where
F: FnOnce(NodeId),
{
let new_node_id = self.get_new_id();
self.merge(other);
f(new_node_id);
Expand Down
17 changes: 13 additions & 4 deletions src/node/iters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ pub struct ChildNodes<'a> {
nodes: Ref<'a, Vec<TreeNode>>,
next_child_id: Option<NodeId>,
rev: bool,

}

impl<'a> ChildNodes<'a> {
Expand All @@ -25,13 +24,19 @@ impl<'a> ChildNodes<'a> {
pub fn new(nodes: Ref<'a, Vec<TreeNode>>, node_id: &NodeId, rev: bool) -> Self {
let first_child = nodes
.get(node_id.value)
.map(|node| if rev { node.last_child} else {node.first_child})
.map(|node| {
if rev {
node.last_child
} else {
node.first_child
}
})
.unwrap_or(None);

ChildNodes {
nodes,
next_child_id: first_child,
rev
rev,
}
}
}
Expand All @@ -43,7 +48,11 @@ impl<'a> Iterator for ChildNodes<'a> {
let current_id = self.next_child_id?;

if let Some(node) = self.nodes.get(current_id.value) {
self.next_child_id = if self.rev { node.prev_sibling } else { node.next_sibling };
self.next_child_id = if self.rev {
node.prev_sibling
} else {
node.next_sibling
};
Some(current_id)
} else {
None
Expand Down
8 changes: 4 additions & 4 deletions src/node/node_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,8 @@ impl<'a> NodeRef<'a> {
where
T: Into<StrTendril>,
{
let fragment = Document::fragment(html);
self.tree.merge_with_fn(fragment.tree, |node_id|{
let fragment = Document::fragment(html);
self.tree.merge_with_fn(fragment.tree, |node_id| {
self.append_prev_siblings(&node_id);
});
self.remove_from_parent();
Expand All @@ -238,7 +238,7 @@ impl<'a> NodeRef<'a> {
T: Into<StrTendril>,
{
let fragment = Document::fragment(html);
self.tree.merge_with_fn(fragment.tree, |node_id|{
self.tree.merge_with_fn(fragment.tree, |node_id| {
self.append_children(&node_id);
});
}
Expand All @@ -249,7 +249,7 @@ impl<'a> NodeRef<'a> {
T: Into<StrTendril>,
{
let fragment = Document::fragment(html);
self.tree.merge_with_fn(fragment.tree, |node_id|{
self.tree.merge_with_fn(fragment.tree, |node_id| {
self.prepend_children(&node_id);
});
}
Expand Down
43 changes: 30 additions & 13 deletions src/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,16 +332,43 @@ impl<'a> Selection<'a> {
/// the nodes from the given selection.
///
/// This follows the same rules as `append`.
///
pub fn replace_with_selection(&self, sel: &Selection) {
//! This is working solution, but it's not optimal yet!
//! Note: goquery's behavior is taken as the basis.

let mut contents: StrTendril = StrTendril::new();
sel.iter().for_each(|s| contents.push_tendril(&s.html()));
let fragment = Document::from(contents);
sel.remove();

for node in self.nodes() {
for prev_sibling in sel.nodes() {
node.append_prev_sibling(&prev_sibling.id);
}
node.tree.merge_with_fn(fragment.tree.clone(), |node_id| {
node.append_prev_siblings(&node_id)
});
}

self.remove()
}

/// Appends the elements in the selection to the end of each element
/// in the set of matched elements.
pub fn append_selection(&self, sel: &Selection) {
//! This is working solution, but it's not optimal yet!
//! Note: goquery's behavior is taken as the basis.

let mut contents: StrTendril = StrTendril::new();
sel.iter().for_each(|s| contents.push_tendril(&s.html()));
let fragment = Document::from(contents);
sel.remove();

for node in self.nodes() {
node.tree.merge_with_fn(fragment.tree.clone(), |node_id| {
node.append_children(&node_id)
});
}
}

/// Parses the html and appends it to the set of matched elements.
pub fn append_html<T>(&self, html: T)
where
Expand Down Expand Up @@ -369,16 +396,6 @@ impl<'a> Selection<'a> {
});
}
}

/// Appends the elements in the selection to the end of each element
/// in the set of matched elements.
pub fn append_selection(&self, sel: &Selection) {
for node in self.nodes() {
for child in sel.nodes() {
node.append_child(&child.id);
}
}
}
}

// traversing methods
Expand Down
12 changes: 12 additions & 0 deletions tests/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ pub static REPLACEMENT_CONTENTS: &str = r#"<!DOCTYPE html>
</body>
</html>"#;

pub static REPLACEMENT_SEL_CONTENTS: &str = r#"<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<div class="ad-content">
<p><span></span></p>
<p><span></span></p>
</div>
<span class="source">example</span>
</body>
</html>"#;

pub static EMPTY_BLOCKS_CONTENTS: &str = r#"<!DOCTYPE html>
<html>
<head></head>
Expand Down
83 changes: 64 additions & 19 deletions tests/selection-manipulation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mod data;

use data::{doc_with_siblings, EMPTY_BLOCKS_CONTENTS};
use data::{doc_with_siblings, EMPTY_BLOCKS_CONTENTS, REPLACEMENT_SEL_CONTENTS};
use dom_query::Document;

#[cfg(target_arch = "wasm32")]
Expand Down Expand Up @@ -50,24 +50,6 @@ fn test_set_html_empty() {
assert_eq!(doc.select("#main").children().length(), 0);
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_replace_with_selection() {
let doc = doc_with_siblings();

let s1 = doc.select("#nf5");
let sel = doc.select("#nf6");

sel.replace_with_selection(&s1);

assert!(sel.is("#nf6"));
assert_eq!(doc.select("#nf6").length(), 0);
assert_eq!(doc.select("#nf5").length(), 1);
s1.append_selection(&sel);
// after appending detached element, it can be matched
assert!(sel.is("#nf6"));
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn remove_descendant_attributes() {
Expand Down Expand Up @@ -159,3 +141,66 @@ fn test_prepend_html_multiple_elements_to_multiple() {

assert_eq!(doc.select(r#"div > .first + .second + .third"#).length(), 2)
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_replace_with_selection() {
let doc = Document::from(REPLACEMENT_SEL_CONTENTS);

let sel_dst = doc.select(".ad-content p span");
let sel_src = doc.select("span.source");

sel_dst.replace_with_selection(&sel_src);
assert_eq!(doc.select(".ad-content .source").length(), 2)
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_append_selection_multiple() {
let doc = Document::from(REPLACEMENT_SEL_CONTENTS);

let sel_dst = doc.select(".ad-content p");
let sel_src = doc.select("span.source");

sel_dst.append_selection(&sel_src);
assert_eq!(doc.select(".ad-content .source").length(), 2);
assert_eq!(doc.select(".ad-content span").length(), 4)
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_replace_with_another_tree_selection() {
let doc_dst = Document::from(REPLACEMENT_SEL_CONTENTS);

let contents_src = r#"
<span class="source">example</span>
<span class="source">example</span>"#;

let doc_src = Document::from(contents_src);

let sel_dst = doc_dst.select(".ad-content p span");
let sel_src = doc_src.select("span.source");

sel_dst.replace_with_selection(&sel_src);
assert_eq!(doc_dst.select(".ad-content .source").length(), 4)
}


#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_append_tree_selection() {
let doc_dst = Document::from(REPLACEMENT_SEL_CONTENTS);

let contents_src = r#"
<span class="source">example</span>
<span class="source">example</span>"#;

let doc_src = Document::from(contents_src);

let sel_dst = doc_dst.select(".ad-content p");
let sel_src = doc_src.select("span.source");

sel_dst.append_selection(&sel_src);
assert_eq!(doc_dst.select(".ad-content .source").length(), 4);
assert_eq!(doc_dst.select(".ad-content span").length(), 6)
}

0 comments on commit 45b44fc

Please sign in to comment.