Repository: jhy/jsoup
Pull request #1908 (2023-02-27 23:56:39)

Bump maven-compiler-plugin from 3.10.1 to 3.11.0
Bumps [maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) from 3.10.1 to 3.11.0.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/apache/maven-compiler-plugin/commit/eeda628b832bf3cc27571e2073f62d582a6d9527"><code>eeda628</code></a> [maven-release-plugin] prepare release maven-compiler-plugin-3.11.0</li>
<li><a href="https://github.com/apache/maven-compiler-plugin/commit/82b799f3501d0dc3ef868859245816c563c46f04"><code>82b799f</code></a> [MCOMPILER-527] Upgrade plexus-java to 1.1.2 (<a href="https://github-redirect.dependabot.com/apache/maven-compiler-plugin/issues/177">#177</a>)</li>
<li><a href="https://github.com/apache/maven-compiler-plugin/commit/f9c2350c885a96638db66fbab4d9180729a31d5a"><code>f9c2350</code></a> [MCOMPILER-526] Fix IT (<a href="https://github-redirect.dependabot.com/apache/maven-compiler-plugin/issues/178">#178</a>)</li>
<li><a href="https://github.com/apache/maven-compiler-plugin/commit/4022bd0f37626124dad394b2e4583fd6768fa74a"><code>4022bd0</code></a> [MCOMPILER-494] - Add a <code>useModulePath</code> switch to the <code>testCompile</code> mojo (<a href="https://github-redirect.dependabot.com/apache/maven-compiler-plugin/issues/119">#119</a>)</li>
<li><a href="https://github.com/apache/maven-compiler-plugin/commit/f4a8a54e116b07e888ac7b6371fa24b7a81517b3"><code>f4a8a54</code></a> [MCOMPILER-525] Incorrect detection of dependency change (<a href="https://github-redirect.dependabot.com/apache/maven-compiler-plugin/issues/172">#172</a>)</li>
<li><a href="https://github.com/apache/maven-compiler-plugin/commit/86b9f5972bcb005305f8abb8fb1f3c0d89df2726"><code>86b9f59</code></a> [MCOMPILER-395] Allow dependency exclusions for 'annotationProcessorPaths' (#...</li>
<li><a href="https://github.com/apache/maven-compiler-plugin/commit/e304ceb91cb625399638f95be41e6c23ca0970d0"><code>e304ceb</code></a> [MCOMPILER-526] Ignore reformat commit for git blame</li>
<li><a href="https://github.com/apache/maven-compiler-plugin/commit/f7a4613eaa2364dcaf10f96f04a6b1afb2feb7ed"><code>f7a4613</code></a> [MCOMPILER-526] Reformat</li>
<li><a href="https://github.com/apache/maven-compiler-plugin/commit/cc78aee657a684af721b3efafd0e1525272d4201"><code>cc78aee</code></a> [MCOMPILER-526] Upgrade to parent 39</li>
<li><a href="https://github.com/apache/maven-compiler-plugin/commit/3dca82f4bf91e747c81ff3fe43e670f7cd7c08e1"><code>3dca82f</code></a> [MCOMPILER-526] Add packages to please the formatter</li>
<li>Additional commits viewable in <a href="https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.10.1...maven-compiler-plugin-3.11.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.apache.maven.plugins:maven-compiler-plugin&package-manager=maven&previous-version=3.10.1&new-version=3.11.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/CHANGES b/CHANGES
index 6b3e0a9b..5fd3b451 100644
--- a/CHANGES
+++ b/CHANGES
@@ -15,6 +15,20 @@ Release 1.16.1 [PENDING]
     again, causing errors when fetched.
     <https://github.com/jhy/jsoup/issues/1902>
 
+  * Bugfix: If the Document.OutputSettings class was initialized, and then Entities.escape(String) called, an NPE may be
+    thrown due to a class loading circular dependency.
+    <https://github.com/jhy/jsoup/issues/1910>
+
+  * Bugfix: when pretty-printing, the first inline Element or Comment in a block would not be wrap-indented if it were
+    preceded by a blank text node.
+    <https://github.com/jhy/jsoup/issues/1906>
+
+  * Bugfix: when pretty-printing a <pre> containing block tags, those tags were incorrectly indented.
+    <https://github.com/jhy/jsoup/issues/1891>
+
+  * Bugfix: <br> tags should be wrap-indented when in block tags (and not when in inline tags).
+    <https://github.com/jhy/jsoup/issues/1911>
+
 Release 1.15.4 [18-Feb-2023]
   * Improvement: added the ability to escape CSS selectors (tags, IDs, classes) to match elements that don't follow
     regular CSS syntax. For example, to match by classname <p class="one.two">, use document.select("p.one\\.two");
diff --git a/pom.xml b/pom.xml
index 94a4409b..f711e44a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,7 @@
 
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <jetty.version>9.4.51.v20230217</jetty.version>
   </properties>
 
   <build>
@@ -40,7 +41,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.10.1</version>
+        <version>3.11.0</version>
         <configuration>
           <source>1.8</source>
           <target>1.8</target>
@@ -341,7 +342,7 @@
       <!-- jetty for webserver integration tests. 9.x is last with Java7 support -->
       <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-server</artifactId>
-      <version>9.4.50.v20221201</version>
+      <version>${jetty.version}</version>
       <scope>test</scope>
     </dependency>
 
@@ -349,7 +350,7 @@
       <!-- jetty for webserver integration tests -->
       <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-servlet</artifactId>
-      <version>9.4.50.v20221201</version>
+      <version>${jetty.version}</version>
       <scope>test</scope>
     </dependency>
 
diff --git a/src/main/java/org/jsoup/nodes/Comment.java b/src/main/java/org/jsoup/nodes/Comment.java
index 8ac8f702..f7fc9f3c 100644
--- a/src/main/java/org/jsoup/nodes/Comment.java
+++ b/src/main/java/org/jsoup/nodes/Comment.java
@@ -38,7 +38,7 @@ public class Comment extends LeafNode {
 
     @Override
 	void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException {
-        if (out.prettyPrint() && ((siblingIndex() == 0 && parentNode instanceof Element && ((Element) parentNode).tag().formatAsBlock()) || (out.outline() )))
+        if (out.prettyPrint() && ((isEffectivelyFirst() && parentNode instanceof Element && ((Element) parentNode).tag().formatAsBlock()) || (out.outline() )))
             indent(accum, depth, out);
         accum
                 .append("<!--")
diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java
index 507fce96..5142fa2b 100644
--- a/src/main/java/org/jsoup/nodes/Element.java
+++ b/src/main/java/org/jsoup/nodes/Element.java
@@ -1670,7 +1670,7 @@ public class Element extends Node {
     }
 
     boolean shouldIndent(final Document.OutputSettings out) {
-        return out.prettyPrint() && isFormatAsBlock(out) && !isInlineable(out);
+        return out.prettyPrint() && isFormatAsBlock(out) && !isInlineable(out) && !preserveWhitespace(parentNode);
     }
 
     @Override
@@ -1701,7 +1701,8 @@ public class Element extends Node {
     void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) throws IOException {
         if (!(childNodes.isEmpty() && tag.isSelfClosing())) {
             if (out.prettyPrint() && (!childNodes.isEmpty() && (
-                    tag.formatAsBlock() || (out.outline() && (childNodes.size()>1 || (childNodes.size()==1 && (childNodes.get(0) instanceof Element))))
+                (tag.formatAsBlock() && !preserveWhitespace(parentNode)) ||
+                    (out.outline() && (childNodes.size()>1 || (childNodes.size()==1 && (childNodes.get(0) instanceof Element))))
             )))
                 indent(accum, depth, out);
             accum.append("</").append(tagName()).append('>');
@@ -1846,9 +1847,11 @@ public class Element extends Node {
     }
 
     private boolean isInlineable(Document.OutputSettings out) {
-        return tag().isInline()
-            && (parent() == null || parent().isBlock())
-            && previousSibling() != null
-            && !out.outline();
+        if (!tag.isInline())
+            return false;
+        return (parent() == null || parent().isBlock())
+            && !isEffectivelyFirst()
+            && !out.outline()
+            && !isNode("br");
     }
 }
diff --git a/src/main/java/org/jsoup/nodes/Entities.java b/src/main/java/org/jsoup/nodes/Entities.java
index d330005d..be447bce 100644
--- a/src/main/java/org/jsoup/nodes/Entities.java
+++ b/src/main/java/org/jsoup/nodes/Entities.java
@@ -7,6 +7,7 @@ import org.jsoup.nodes.Document.OutputSettings;
 import org.jsoup.parser.CharacterReader;
 import org.jsoup.parser.Parser;
 
+import javax.annotation.Nullable;
 import java.io.IOException;
 import java.nio.charset.CharsetEncoder;
 import java.util.Arrays;
@@ -26,7 +27,6 @@ public class Entities {
     static final int codepointRadix = 36;
     private static final char[] codeDelims = {',', ';'};
     private static final HashMap<String, String> multipoints = new HashMap<>(); // name -> multiple character references
-    private static final OutputSettings DefaultOutput = new OutputSettings();
 
     public enum EscapeMode {
         /**
@@ -157,8 +157,11 @@ public class Entities {
      * @return the escaped string
      */
     public static String escape(String string) {
+        if (DefaultOutput == null)
+            DefaultOutput = new OutputSettings();
         return escape(string, DefaultOutput);
     }
+    private static @Nullable OutputSettings DefaultOutput; // lazy-init, to break circular dependency with OutputSettings
 
     // this method does a lot, but other breakups cause rescanning and stringbuilder generations
     static void escape(Appendable accum, String string, OutputSettings out,
diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java
index fc5ac3bf..851bd8a5 100644
--- a/src/main/java/org/jsoup/nodes/Node.java
+++ b/src/main/java/org/jsoup/nodes/Node.java
@@ -762,6 +762,16 @@ public abstract class Node implements Cloneable {
         return normalName().equals(normalName);
     }
 
+    /** Test if this node is the first child, or first following blank text. */
+    final boolean isEffectivelyFirst() {
+        if (siblingIndex == 0) return true;
+        if (siblingIndex == 1) {
+            final Node prev = previousSibling();
+            return prev instanceof TextNode && (((TextNode) prev).isBlank());
+        }
+        return false;
+    }
+
     /**
      * Gets this node's outer HTML.
      * @return outer HTML.
diff --git a/src/main/java/org/jsoup/parser/Tag.java b/src/main/java/org/jsoup/parser/Tag.java
index d573033a..366bc63e 100644
--- a/src/main/java/org/jsoup/parser/Tag.java
+++ b/src/main/java/org/jsoup/parser/Tag.java
@@ -242,7 +242,7 @@ public class Tag implements Cloneable {
     };
     private static final String[] inlineTags = {
             "object", "base", "font", "tt", "i", "b", "u", "big", "small", "em", "strong", "dfn", "code", "samp", "kbd",
-            "var", "cite", "abbr", "time", "acronym", "mark", "ruby", "rt", "rp", "a", "img", "br", "wbr", "map", "q",
+            "var", "cite", "abbr", "time", "acronym", "mark", "ruby", "rt", "rp", "rtc", "a", "img", "br", "wbr", "map", "q",
             "sub", "sup", "bdo", "iframe", "embed", "span", "input", "select", "textarea", "label", "button", "optgroup",
             "option", "legend", "datalist", "keygen", "output", "progress", "meter", "area", "param", "source", "track",
             "summary", "command", "device", "area", "basefont", "bgsound", "menuitem", "param", "source", "track",


Test patch:
diff --git a/src/test/java/org/jsoup/nodes/CommentTest.java b/src/test/java/org/jsoup/nodes/CommentTest.java
index fe1c1361..c622f184 100644
--- a/src/test/java/org/jsoup/nodes/CommentTest.java
+++ b/src/test/java/org/jsoup/nodes/CommentTest.java
@@ -41,6 +41,20 @@ public class CommentTest {
         assertEquals("<!-- a simple comment -->", c1.outerHtml());
     }
 
+    @Test void stableIndentInBlock() {
+        String html = "<div><!-- comment --> Text</div><p><!-- comment --> Text</p>";
+        Document doc = Jsoup.parse(html);
+        String out = doc.body().html();
+        assertEquals("<div>\n" +
+            " <!-- comment --> Text\n" +
+            "</div>\n" +
+            "<p><!-- comment --> Text</p>", out);
+
+        Document doc2 = Jsoup.parse(out);
+        String out2 = doc2.body().html();
+        assertEquals(out, out2);
+    }
+
     @Test
     public void testClone() {
         Comment c1 = comment.clone();
diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java
index 0887a4e7..0bb433ce 100644
--- a/src/test/java/org/jsoup/nodes/ElementTest.java
+++ b/src/test/java/org/jsoup/nodes/ElementTest.java
@@ -157,6 +157,14 @@ public class ElementTest {
         assertEquals("<pre><code><span><b>code\n\ncode</b></span></code></pre>", doc.body().html());
     }
 
+    @Test void doesNotWrapBlocksInPre() {
+        // https://github.com/jhy/jsoup/issues/1891
+        String h = "<pre><span><foo><div>TEST\n TEST</div></foo></span></pre>";
+        Document doc = Jsoup.parse(h);
+        assertEquals("TEST\n TEST", doc.wholeText());
+        assertEquals(h, doc.body().html());
+    }
+
     @Test
     public void testBrHasSpace() {
         Document doc = Jsoup.parse("<p>Hello<br>there</p>");
@@ -2306,6 +2314,17 @@ public class ElementTest {
         assertEquals("<div>\n <br>\n</div>", doc.body().html()); // not div\n br\n \n/div
     }
 
+    @Test void prettyprintBrWhenNotFirstChild() {
+        // https://github.com/jhy/jsoup/issues/1911
+        String h = "<div><p><br>Foo</p><br></div>";
+        Document doc = Jsoup.parse(h);
+        assertEquals("<div>\n" +
+            " <p><br>\n  Foo</p>\n" +
+            " <br>\n" +
+            "</div>", doc.body().html());
+        // br gets wrapped if in div, but not in p (block vs inline), but always wraps after
+    }
+
     @Test void preformatFlowsToChildTextNodes() {
         // https://github.com/jhy/jsoup/issues/1776
         String html = "<div><pre>One\n<span>\nTwo</span>\n <span>  \nThree</span>\n <span>Four <span>Five</span>\n  Six\n</pre>";
@@ -2343,6 +2362,23 @@ public class ElementTest {
         assertEquals(expectOwn, div.child(0).wholeOwnText());
     }
 
+    @Test void inlineInBlockShouldIndent() {
+        // was inconsistent between <div>\n<span> and <div><span> - former would print inline, latter would wrap(!)
+        String html = "<div>One <span>Hello</span><span>!</span></div><div>\n<span>There</span></div><div> <span>Now</span></div>";
+        Document doc = Jsoup.parse(html);
+        assertEquals(
+            "<div>\n" +
+                " One <span>Hello</span><span>!</span>\n" +
+                "</div>\n" +
+                "<div>\n" +
+                " <span>There</span>\n" +
+                "</div>\n" +
+                "<div>\n" +
+                " <span>Now</span>\n" +
+                "</div>",
+            doc.body().html());
+    }
+
     @Test void testExpectFirst() {
         Document doc = Jsoup.parse("<p>One</p><p>Two <span>Three</span> <span>Four</span>");
 
@@ -2445,7 +2481,9 @@ public class ElementTest {
     @Test void divAInlineable() {
         String html = "<body><div> <a>Text</a>";
         Document doc = Jsoup.parse(html);
-        assertEquals("<div><a>Text</a>\n</div>", doc.body().html());
+        assertEquals("<div>\n" +
+            " <a>Text</a>\n" +
+            "</div>", doc.body().html());
     }
 
     @Test void noDanglingSpaceAfterCustomElement() {
@@ -2657,4 +2695,10 @@ public class ElementTest {
         doc.body().outerHtml(builder);
         assertEquals("<body>\n <div>\n  One\n </div>\n</body>", builder.toString());
     }
-}
\ No newline at end of file
+
+    @Test void rubyInline() {
+        String html = "<ruby>T<rp>(</rp><rtc>!</rtc><rt>)</rt></ruby>";
+        Document doc = Jsoup.parse(html);
+        assertEquals(html, doc.body().html());
+    }
+}
diff --git a/src/test/java/org/jsoup/parser/HtmlParserTest.java b/src/test/java/org/jsoup/parser/HtmlParserTest.java
index 1f83518e..26b546d5 100644
--- a/src/test/java/org/jsoup/parser/HtmlParserTest.java
+++ b/src/test/java/org/jsoup/parser/HtmlParserTest.java
@@ -1247,7 +1247,7 @@ public class HtmlParserTest {
         File in = ParseTest.getFile("/htmltests/comments.html");
         Document doc = Jsoup.parse(in, "UTF-8");
 
-        assertEquals("<!--?xml version=\"1.0\" encoding=\"utf-8\"?--><!-- so --> <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><!-- what --> <html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"><!-- now --> <head><!-- then --> <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"> <title>A Certain Kind of Test</title> </head> <body> <h1>Hello</h1>h1&gt; (There is a UTF8 hidden BOM at the top of this file.) </body> </html>",
+        assertEquals("<!--?xml version=\"1.0\" encoding=\"utf-8\"?--><!-- so --> <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><!-- what --> <html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"> <!-- now --> <head> <!-- then --> <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"> <title>A Certain Kind of Test</title> </head> <body> <h1>Hello</h1>h1&gt; (There is a UTF8 hidden BOM at the top of this file.) </body> </html>",
             StringUtil.normaliseWhitespace(doc.html()));
 
         assertEquals("A Certain Kind of Test", doc.head().select("title").text());
diff --git a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java
index f05ee268..c2dbfe31 100644
--- a/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java
+++ b/src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java
@@ -75,7 +75,8 @@ public class HtmlTreeBuilderStateTest {
         String s = Jsoup.parse(html).toString();
         assertEquals("<html>\n" +
             " <head></head>\n" +
-            " <body><a href=\"#1\"> </a>\n" +
+            " <body>\n" +
+            "  <a href=\"#1\"> </a>\n" +
             "  <div>\n" +
             "   <a href=\"#1\"> </a><a href=\"#2\">child</a>\n" +
             "  </div>\n" +
@@ -99,7 +100,8 @@ public class HtmlTreeBuilderStateTest {
         String s = Jsoup.parse(html).toString();
         assertEquals("<html>\n" +
             " <head></head>\n" +
-            " <body><a href=\"#1\"> </a>\n" +
+            " <body>\n" +
+            "  <a href=\"#1\"> </a>\n" +
             "  <div>\n" +
             "   <a href=\"#1\"> </a>\n" +
             "   <div>\n" +
diff --git a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java
index 227dd096..d359a528 100644
--- a/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java
+++ b/src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java
@@ -94,7 +94,7 @@ public class XmlTreeBuilderTest {
     public void testDoesNotForceSelfClosingKnownTags() {
         // html will force "<br>one</br>" to logically "<br />One<br />". XML should be stay "<br>one</br> -- don't recognise tag.
         Document htmlDoc = Jsoup.parse("<br>one</br>");
-        assertEquals("<br>\none<br>", htmlDoc.body().html());
+        assertEquals("<br>\none\n<br>", htmlDoc.body().html());
 
         Document xmlDoc = Jsoup.parse("<br>one</br>", "", Parser.xmlParser());
         assertEquals("<br>one</br>", xmlDoc.html());
diff --git a/src/test/java/org/jsoup/select/ElementsTest.java b/src/test/java/org/jsoup/select/ElementsTest.java
index eab17c2c..d1895752 100644
--- a/src/test/java/org/jsoup/select/ElementsTest.java
+++ b/src/test/java/org/jsoup/select/ElementsTest.java
@@ -193,7 +193,9 @@ public class ElementsTest {
         String h = "<div><font>One</font> <font><a href=\"/\">Two</a></font></div";
         Document doc = Jsoup.parse(h);
         doc.select("font").unwrap();
-        assertEquals("<div>One <a href=\"/\">Two</a></div>", TextUtil.stripNewlines(doc.body().html()));
+        assertEquals("<div>\n" +
+            " One <a href=\"/\">Two</a>\n" +
+            "</div>", doc.body().html());
     }
 
     @Test public void unwrapP() {