Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Improved `W3CDom` conversion performance for documents with many nested namespace declarations. The W3C converter now uses the same optimized namespace tracking as the XML parser. [#2559](https://github.com/jhy/jsoup/pull/2559)
* Improved `W3CDom` XML conversion to retain processing instructions, comments outside the root element, and CDATA sections, which were previously dropped or converted to text. [#2572](https://github.com/jhy/jsoup/issues/2572)
* DOM mutation methods, including child insertion and replacement, now reject operations that would create a cycle, such as making a node its own child or moving an ancestor beneath a descendant. [#2552](https://github.com/jhy/jsoup/issues/2552)
* Added `Elements#before(Node)`, `after(Node)`, `prepend(Node)`, and `append(Node)` to match the existing HTML string methods. [#953](https://github.com/jhy/jsoup/issues/953)
* XML serialization now repairs element and attribute names that start with an invalid character, rather than outputting `<null>` elements or dropping attributes. For example, an attribute named `1a` is written as `_1a`. Additional leading underscores keep repaired attribute names unique if they conflict with another attribute. [#2573](https://github.com/jhy/jsoup/issues/2573)

### Changes
Expand Down
61 changes: 59 additions & 2 deletions src/main/java/org/jsoup/select/Elements.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.BiConsumer;
import java.util.function.UnaryOperator;

/**
Expand Down Expand Up @@ -314,6 +313,17 @@ public Elements prepend(String html) {
}
return this;
}

/**
Add the supplied node to the start of each matched element's inner HTML. The node is cloned for each target.

@param node the node to add inside each element, before the existing HTML
@return this, for chaining
@see Element#prependChild(Node)
*/
public Elements prepend(Node node) {
return insert(node, Element::prependChild);
}

/**
* Add the supplied HTML to the end of each matched element's inner HTML.
Expand All @@ -328,6 +338,17 @@ public Elements append(String html) {
return this;
}

/**
Add the supplied node to the end of each matched element's inner HTML. The node is cloned for each target.

@param node the node to add inside each element, after the existing HTML
@return this, for chaining
@see Element#appendChild(Node)
*/
public Elements append(Node node) {
return insert(node, Element::appendChild);
}

/**
Insert the supplied HTML before each matched element's outer HTML.

Expand All @@ -341,6 +362,17 @@ public Elements before(String html) {
return this;
}

/**
Insert the supplied node before each matched element's outer HTML. The node is cloned for each target.

@param node the node to insert before each element
@return this, for chaining
@see Element#before(Node)
*/
public Elements before(Node node) {
return insert(node, Element::before);
}

/**
Insert the supplied HTML after each matched element's outer HTML.

Expand All @@ -354,6 +386,31 @@ public Elements after(String html) {
return this;
}

/**
Insert the supplied node after each matched element's outer HTML. The node is cloned for each target.

@param node the node to insert after each element
@return this, for chaining
@see Element#after(Node)
*/
public Elements after(Node node) {
return insert(node, Element::after);
}

/**
Applies a node insertion to each matched element, cloning the node for each target.

@param node the node to insert
@param inserter the insertion operation
@return this, for chaining
*/
private Elements insert(Node node, BiConsumer<Element, Node> inserter) {
Validate.notNull(node);
for (Element element : this)
inserter.accept(element, node.clone());
return this;
}

/**
Wrap the supplied HTML around each matched elements. For example, with HTML
{@code <p><b>This</b> is <b>Jsoup</b></p>},
Expand Down
28 changes: 28 additions & 0 deletions src/test/java/org/jsoup/select/ElementsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,40 @@ public class ElementsTest {
assertEquals("<p>This <span>foo</span><a>is</a> <span>foo</span><a>jsoup</a>.</p>", TextUtil.stripNewlines(doc.body().html()));
}

@Test public void beforeNode() {
Document doc = Jsoup.parse("<p>This <a>is</a> <a>jsoup</a>.</p>");
Element span = new Element("span").text("foo");
doc.select("a").before(span);
assertEquals("<p>This <span>foo</span><a>is</a> <span>foo</span><a>jsoup</a>.</p>", TextUtil.stripNewlines(doc.body().html()));
assertNull(span.parent()); // cloned per target; original is left alone
assertNotSame(span, doc.selectFirst("span"));
}

@Test public void after() {
Document doc = Jsoup.parse("<p>This <a>is</a> <a>jsoup</a>.</p>");
doc.select("a").after("<span>foo</span>");
assertEquals("<p>This <a>is</a><span>foo</span> <a>jsoup</a><span>foo</span>.</p>", TextUtil.stripNewlines(doc.body().html()));
}

@Test public void afterNode() {
Document doc = Jsoup.parse("<p>This <a>is</a> <a>jsoup</a>.</p>");
Element span = new Element("span").text("foo");
doc.select("a").after(span);
assertEquals("<p>This <a>is</a><span>foo</span> <a>jsoup</a><span>foo</span>.</p>", TextUtil.stripNewlines(doc.body().html()));
assertNull(span.parent());
}

@Test public void prependAppendNode() {
Document doc = Jsoup.parse("<p>One</p><p>Two</p><p>Three</p>");
Elements ps = doc.select("p");
Element bold = new Element("b").text("Bold");
Element ital = new Element("i").text("Ital");
ps.prepend(bold).append(ital);
assertEquals("<p><b>Bold</b>Two<i>Ital</i></p>", TextUtil.stripNewlines(ps.get(1).outerHtml()));
assertNull(bold.parent());
assertNull(ital.parent());
}

@Test public void wrap() {
String h = "<p><b>This</b> is <b>jsoup</b></p>";
Document doc = Jsoup.parse(h);
Expand Down
Loading