From 1a21165a792e178e3d7ef3c8bb7a5df4a63b5396 Mon Sep 17 00:00:00 2001 From: Jeremy Landis Date: Sat, 16 Oct 2021 21:00:57 -0400 Subject: [PATCH] [enhance] Expose maxPaddingWidth in OutputSettings with default of 30 retained --- CHANGES | 4 +++ .../java/org/jsoup/internal/StringUtil.java | 21 +++++++++--- src/main/java/org/jsoup/nodes/Document.java | 24 ++++++++++++- src/main/java/org/jsoup/nodes/Node.java | 2 +- .../org/jsoup/internal/StringUtilTest.java | 34 ++++++++++++++++--- 5 files changed, 73 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index eda0d2bc46..51ad5ee147 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,10 @@ jsoup changelog case. + * Improvement: allow maxPaddingWidth in OutputSettings for pretty printing. This will continue to default to 30 + so very deeply nested nodes don't get insane padding amounts but now allows for user decision. Using -1 will + result in original unlimited length. + *** Release 1.14.3 [2021-Sep-30] * Improvement: added native XPath support in Element#selectXpath(String) diff --git a/src/main/java/org/jsoup/internal/StringUtil.java b/src/main/java/org/jsoup/internal/StringUtil.java index eed2422062..d8913e5dbe 100644 --- a/src/main/java/org/jsoup/internal/StringUtil.java +++ b/src/main/java/org/jsoup/internal/StringUtil.java @@ -16,11 +16,10 @@ notice. */ public final class StringUtil { - // memoised padding up to 21 + // memoised padding up to 21 (blocks 0 to 20 spaces) static final String[] padding = {"", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "}; - private static final int maxPaddingWidth = 30; // so very deeply nested nodes don't get insane padding amounts /** * Join a collection of strings by a separator @@ -115,17 +114,29 @@ public String complete() { } /** - * Returns space padding (up to a max of 30). + * Returns space padding (up to a max of 30). Use {@link #padding(int, int)} if you need + * more than 30 spaces padded. * @param width amount of padding desired * @return string of spaces * width - */ + */ public static String padding(int width) { + return padding(width, 30); + } + + /** + * Returns space padding (up to a max of maxPaddingWidth - default 30). + * @param width amount of padding desired + * @param maxPaddingWidth amount of max space padding with default set at 30 and -1 means no max. + * @return string of spaces * width + */ + public static String padding(int width, int maxPaddingWidth) { if (width < 0) throw new IllegalArgumentException("width must be > 0"); if (width < padding.length) return padding[width]; - width = Math.min(width, maxPaddingWidth); + if (maxPaddingWidth != -1) + width = Math.min(width, maxPaddingWidth); char[] out = new char[width]; for (int i = 0; i < width; i++) out[i] = ' '; diff --git a/src/main/java/org/jsoup/nodes/Document.java b/src/main/java/org/jsoup/nodes/Document.java index 1e1217bba7..6e5d8e3202 100644 --- a/src/main/java/org/jsoup/nodes/Document.java +++ b/src/main/java/org/jsoup/nodes/Document.java @@ -412,6 +412,7 @@ public enum Syntax {html, xml} private boolean prettyPrint = true; private boolean outline = false; private int indentAmount = 1; + private int maxPaddingWidth = 30; private Syntax syntax = Syntax.html; public OutputSettings() {} @@ -560,6 +561,27 @@ public OutputSettings indentAmount(int indentAmount) { return this; } + /** + * Get the current max padding amount, used when pretty printing + * so very deeply nested nodes don't get insane padding amounts. + * @return the current indent amount + */ + public int maxPaddingWidth() { + return maxPaddingWidth; + } + + /** + * Set the max padding amount for pretty printing so very deeply nested nodes don't get insane padding amounts. + * @param maxPaddingWidth number of spaces to use for indenting each level of nested nodes. Must be {@literal >=} -1. + * Default is 30 and -1 means unlimited. + * @return this, for chaining + */ + public OutputSettings maxPaddingWidth(int maxPaddingWidth) { + Validate.isTrue(maxPaddingWidth >= -1); + this.maxPaddingWidth = maxPaddingWidth; + return this; + } + @Override public OutputSettings clone() { OutputSettings clone; @@ -570,7 +592,7 @@ public OutputSettings clone() { } clone.charset(charset.name()); // new charset and charset encoder clone.escapeMode = Entities.EscapeMode.valueOf(escapeMode.name()); - // indentAmount, prettyPrint are primitives so object.clone() will handle + // indentAmount, maxPaddingWidth, and prettyPrint are primitives so object.clone() will handle return clone; } } diff --git a/src/main/java/org/jsoup/nodes/Node.java b/src/main/java/org/jsoup/nodes/Node.java index 5d62478408..27886f46b0 100644 --- a/src/main/java/org/jsoup/nodes/Node.java +++ b/src/main/java/org/jsoup/nodes/Node.java @@ -683,7 +683,7 @@ public String toString() { } protected void indent(Appendable accum, int depth, Document.OutputSettings out) throws IOException { - accum.append('\n').append(StringUtil.padding(depth * out.indentAmount())); + accum.append('\n').append(StringUtil.padding(depth * out.indentAmount(), out.maxPaddingWidth())); } /** diff --git a/src/test/java/org/jsoup/internal/StringUtilTest.java b/src/test/java/org/jsoup/internal/StringUtilTest.java index 1956084bae..dc29bdde41 100644 --- a/src/test/java/org/jsoup/internal/StringUtilTest.java +++ b/src/test/java/org/jsoup/internal/StringUtilTest.java @@ -20,11 +20,35 @@ public void join() { } @Test public void padding() { - assertEquals("", StringUtil.padding(0)); - assertEquals(" ", StringUtil.padding(1)); - assertEquals(" ", StringUtil.padding(2)); - assertEquals(" ", StringUtil.padding(15)); - assertEquals(" ", StringUtil.padding(45)); // we tap out at 30 + // memoization is up to 21 blocks (0 to 20 spaces) and exits early before min checks making maxPaddingWidth unused + assertEquals("", StringUtil.padding(0, -1)); + assertEquals(" ", StringUtil.padding(20, -1)); + + // this test escapes memoization and continues through + assertEquals(" ", StringUtil.padding(21, -1)); + + // this test escapes memoization and using unlimited length (-1) will allow requested spaces + assertEquals(" ", StringUtil.padding(30, -1)); + assertEquals(" ", StringUtil.padding(45, -1)); + + // we tap out at 0 for this test + assertEquals("", StringUtil.padding(0, 0)); + + // we tap out at 5 for this test because we do not escape memoization + assertEquals(" ", StringUtil.padding(5, 0)); + + // as memoization is escaped, setting zero for max padding will not allow any requested width + assertEquals("", StringUtil.padding(21, 0)); + + // we tap out at 30 for these tests making > 30 use 30 + assertEquals("", StringUtil.padding(0, 30)); + assertEquals(" ", StringUtil.padding(1, 30)); + assertEquals(" ", StringUtil.padding(2, 30)); + assertEquals(" ", StringUtil.padding(15, 30)); + assertEquals(" ", StringUtil.padding(45, 30)); + + // Testing deprecated version capped at 30 + assertEquals(" ", StringUtil.padding(45)); } @Test public void paddingInACan() {