Skip to content

Commit eff1521

Browse files
committed
Clear child nodes' parent on parent.empty()
Fixes #2013
1 parent 545145a commit eff1521

File tree

3 files changed

+54
-1
lines changed

3 files changed

+54
-1
lines changed

CHANGES

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ Release 1.16.2 [PENDING]
6464
Further, a StackOverflowError may occur when running the query.
6565
<https://github.com/jhy/jsoup/issues/2001>
6666

67+
* Bugfix: appending a node back to its original Element after empty() would throw an Index out of bounds exception.
68+
Also, now the child nodes that were removed have their parent node cleared, fully detaching them from the original
69+
parent.
70+
<https://github.com/jhy/jsoup/issues/2013>
71+
6772
* Change: removed previously deprecated methods Document#normalise, Element#forEach(org.jsoup.helper.Consumer<>),
6873
Node#forEach(org.jsoup.helper.Consumer<>), and the org.jsoup.helper.Consumer interface; the latter being a
6974
previously required compatibility shim prior to Android's de-sugaring support.

src/main/java/org/jsoup/nodes/Element.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -829,11 +829,16 @@ public Element after(Node node) {
829829
}
830830

831831
/**
832-
* Remove all the element's child nodes. Any attributes are left as-is.
832+
* Remove all the element's child nodes. Any attributes are left as-is. Each child node has its parent set to
833+
* {@code null}.
833834
* @return this element
834835
*/
835836
@Override
836837
public Element empty() {
838+
// Detach each of the children -> parent links:
839+
for (Node child : childNodes) {
840+
child.parentNode = null;
841+
}
837842
childNodes.clear();
838843
return this;
839844
}

src/test/java/org/jsoup/nodes/ElementTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2773,4 +2773,47 @@ void prettySerializationRoundTrips(Document.OutputSettings settings) {
27732773
"</table>", out);
27742774
// todo - I would prefer the </td> to wrap down there - but need to reimplement pretty printer to simplify and track indented state
27752775
}
2776+
2777+
@Test void emptyDetachesChildren() {
2778+
String html = "<div><p>One<p>Two</p>Three</div>";
2779+
Document doc = Jsoup.parse(html);
2780+
Element div = doc.expectFirst("div");
2781+
assertEquals(3, div.childNodeSize());
2782+
2783+
List<Node> childNodes = div.childNodes();
2784+
2785+
div.empty();
2786+
assertEquals(0, div.childNodeSize());
2787+
assertEquals(3, childNodes.size()); // copied before removing
2788+
for (Node childNode : childNodes) {
2789+
assertNull(childNode.parentNode);
2790+
}
2791+
2792+
Element p = (Element) childNodes.get(0);
2793+
assertEquals(p, p.childNode(0).parentNode()); // TextNode "One" still has parent p, as detachment is only on div element
2794+
}
2795+
2796+
@Test void emptyAndAddPreviousChild() {
2797+
String html = "<div><p>One<p>Two<p>Three</div>";
2798+
Document doc = Jsoup.parse(html);
2799+
Element div = doc.expectFirst("div");
2800+
Element p = div.expectFirst("p");
2801+
div
2802+
.empty()
2803+
.appendChild(p);
2804+
2805+
assertEquals("<p>One</p>", div.html());
2806+
}
2807+
2808+
@Test void emptyAndAddPreviousDescendant() {
2809+
String html = "<header><div><p>One<p>Two<p>Three</div></header>";
2810+
Document doc = Jsoup.parse(html);
2811+
Element header = doc.expectFirst("header");
2812+
Element p = header.expectFirst("p");
2813+
header
2814+
.empty()
2815+
.appendChild(p);
2816+
2817+
assertEquals("<p>One</p>", header.html());
2818+
}
27762819
}

0 commit comments

Comments
 (0)