Skip to content

Commit

Permalink
Clear child nodes' parent on parent.empty()
Browse files Browse the repository at this point in the history
Fixes #2013
  • Loading branch information
jhy committed Oct 18, 2023
1 parent 545145a commit eff1521
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ Release 1.16.2 [PENDING]
Further, a StackOverflowError may occur when running the query.
<https://github.com/jhy/jsoup/issues/2001>

* Bugfix: appending a node back to its original Element after empty() would throw an Index out of bounds exception.
Also, now the child nodes that were removed have their parent node cleared, fully detaching them from the original
parent.
<https://github.com/jhy/jsoup/issues/2013>

* Change: removed previously deprecated methods Document#normalise, Element#forEach(org.jsoup.helper.Consumer<>),
Node#forEach(org.jsoup.helper.Consumer<>), and the org.jsoup.helper.Consumer interface; the latter being a
previously required compatibility shim prior to Android's de-sugaring support.
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/org/jsoup/nodes/Element.java
Original file line number Diff line number Diff line change
Expand Up @@ -829,11 +829,16 @@ public Element after(Node node) {
}

/**
* Remove all the element's child nodes. Any attributes are left as-is.
* Remove all the element's child nodes. Any attributes are left as-is. Each child node has its parent set to
* {@code null}.
* @return this element
*/
@Override
public Element empty() {
// Detach each of the children -> parent links:
for (Node child : childNodes) {
child.parentNode = null;
}
childNodes.clear();
return this;
}
Expand Down
43 changes: 43 additions & 0 deletions src/test/java/org/jsoup/nodes/ElementTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2773,4 +2773,47 @@ void prettySerializationRoundTrips(Document.OutputSettings settings) {
"</table>", out);
// todo - I would prefer the </td> to wrap down there - but need to reimplement pretty printer to simplify and track indented state
}

@Test void emptyDetachesChildren() {
String html = "<div><p>One<p>Two</p>Three</div>";
Document doc = Jsoup.parse(html);
Element div = doc.expectFirst("div");
assertEquals(3, div.childNodeSize());

List<Node> childNodes = div.childNodes();

div.empty();
assertEquals(0, div.childNodeSize());
assertEquals(3, childNodes.size()); // copied before removing
for (Node childNode : childNodes) {
assertNull(childNode.parentNode);
}

Element p = (Element) childNodes.get(0);
assertEquals(p, p.childNode(0).parentNode()); // TextNode "One" still has parent p, as detachment is only on div element
}

@Test void emptyAndAddPreviousChild() {
String html = "<div><p>One<p>Two<p>Three</div>";
Document doc = Jsoup.parse(html);
Element div = doc.expectFirst("div");
Element p = div.expectFirst("p");
div
.empty()
.appendChild(p);

assertEquals("<p>One</p>", div.html());
}

@Test void emptyAndAddPreviousDescendant() {
String html = "<header><div><p>One<p>Two<p>Three</div></header>";
Document doc = Jsoup.parse(html);
Element header = doc.expectFirst("header");
Element p = header.expectFirst("p");
header
.empty()
.appendChild(p);

assertEquals("<p>One</p>", header.html());
}
}

0 comments on commit eff1521

Please sign in to comment.