From fbf87b8495d00723e8a62ba0824fd7df7fdf0ce5 Mon Sep 17 00:00:00 2001 From: Dan Wyand Date: Fri, 14 Jul 2023 14:34:44 -0400 Subject: [PATCH 001/345] add column width to TableCell nodes The width is determined by the number of dash and colon characters in the separator line of a table, and passed on to the consumer as an integer. This permits consumers of the AST to render tables that are more proportional the width in the source markdown. No changes have been made to the HTML render to make use of the width information, but the test for this feature demonstrates a possible usage. Should a cell be be created that is further to the right than the parsed separator columns are (i.e., when alignment would be null), then the width will be reported as 0. --- .../commonmark/ext/gfm/tables/TableCell.java | 12 ++++++ .../gfm/tables/internal/TableBlockParser.java | 39 +++++++++++++++---- .../commonmark/ext/gfm/tables/TablesTest.java | 36 +++++++++++++++++ 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java index 61880c6c3..623e30062 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java @@ -9,6 +9,7 @@ public class TableCell extends CustomNode { private boolean header; private Alignment alignment; + private int width; /** * @return whether the cell is a header or not @@ -32,6 +33,17 @@ public void setAlignment(Alignment alignment) { this.alignment = alignment; } + /** + * @return the cell width + */ + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + /** * How the cell is aligned horizontally. */ diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index b7cea14db..2ffa53109 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java @@ -17,11 +17,11 @@ public class TableBlockParser extends AbstractBlockParser { private final TableBlock block = new TableBlock(); private final List rowLines = new ArrayList<>(); - private final List columns; + private final List columns; private boolean canHaveLazyContinuationLines = true; - private TableBlockParser(List columns, SourceLine headerLine) { + private TableBlockParser(List columns, SourceLine headerLine) { this.columns = columns; this.rowLines.add(headerLine); } @@ -120,7 +120,9 @@ private TableCell parseCell(SourceLine cell, int column, InlineParser inlinePars } if (column < columns.size()) { - tableCell.setAlignment(columns.get(column)); + TableCellInfo cellInfo = columns.get(column); + tableCell.setAlignment(cellInfo.getAlignment()); + tableCell.setWidth(cellInfo.getWidth()); } CharSequence content = cell.getContent(); @@ -187,11 +189,12 @@ private static List split(SourceLine line) { // -|- // |-|-| // --- | --- - private static List parseSeparator(CharSequence s) { - List columns = new ArrayList<>(); + private static List parseSeparator(CharSequence s) { + List columns = new ArrayList<>(); int pipes = 0; boolean valid = false; int i = 0; + int width = 0; while (i < s.length()) { char c = s.charAt(i); switch (c) { @@ -216,10 +219,12 @@ private static List parseSeparator(CharSequence s) { if (c == ':') { left = true; i++; + width++; } boolean haveDash = false; while (i < s.length() && s.charAt(i) == '-') { i++; + width++; haveDash = true; } if (!haveDash) { @@ -229,8 +234,10 @@ private static List parseSeparator(CharSequence s) { if (i < s.length() && s.charAt(i) == ':') { right = true; i++; + width++; } - columns.add(getAlignment(left, right)); + columns.add(new TableCellInfo(getAlignment(left, right), width)); + width = 0; // Next, need another pipe pipes = 0; break; @@ -270,7 +277,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar if (paragraphLines.size() == 1 && Parsing.find('|', paragraphLines.get(0).getContent(), 0) != -1) { SourceLine line = state.getLine(); SourceLine separatorLine = line.substring(state.getIndex(), line.getContent().length()); - List columns = parseSeparator(separatorLine.getContent()); + List columns = parseSeparator(separatorLine.getContent()); if (columns != null && !columns.isEmpty()) { SourceLine paragraph = paragraphLines.get(0); List headerCells = split(paragraph); @@ -284,4 +291,22 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar return BlockStart.none(); } } + + private static class TableCellInfo { + private final TableCell.Alignment alignment; + private final int width; + + public TableCell.Alignment getAlignment() { + return alignment; + } + + public int getWidth() { + return width; + } + + public TableCellInfo(TableCell.Alignment alignment, int width) { + this.alignment = alignment; + this.width = width; + } + } } diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java index bef3b8b6c..b03714d9a 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -749,6 +749,42 @@ public void setAttributes(Node node, String tagName, Map attribu "\n")); } + @Test + public void columnWidthIsRecorded() { + AttributeProviderFactory factory = new AttributeProviderFactory() { + @Override + public AttributeProvider create(AttributeProviderContext context) { + return new AttributeProvider() { + @Override + public void setAttributes(Node node, String tagName, Map attributes) { + if (node instanceof TableCell && "th".equals(tagName)) { + attributes.put("width", ((TableCell) node).getWidth() + "em"); + } + } + }; + } + }; + HtmlRenderer renderer = HtmlRenderer.builder() + .attributeProviderFactory(factory) + .extensions(EXTENSIONS) + .build(); + String rendered = renderer.render(PARSER.parse("Abc|Def\n-----|---\n1|2")); + assertThat(rendered, is("\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
AbcDef
12
\n")); + } + @Test public void sourceSpans() { Parser parser = Parser.builder() From 654203286f8e6b1900a4e564613ed361e8a36449 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 9 Jan 2024 16:48:57 +1100 Subject: [PATCH 002/345] Add comment to TextContentRenderer, fix typo --- .../org/commonmark/renderer/text/TextContentRenderer.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java index aacfbb82a..9dd5918af 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java @@ -9,6 +9,9 @@ import java.util.ArrayList; import java.util.List; +/** + * Renders nodes to plain text content with minimal markup-like additions. + */ public class TextContentRenderer implements Renderer { private final boolean stripNewlines; @@ -30,7 +33,7 @@ public NodeRenderer create(TextContentNodeRendererContext context) { } /** - * Create a new builder for configuring an {@link TextContentRenderer}. + * Create a new builder for configuring a {@link TextContentRenderer}. * * @return a builder */ @@ -52,7 +55,7 @@ public String render(Node node) { } /** - * Builder for configuring an {@link TextContentRenderer}. See methods for default configuration. + * Builder for configuring a {@link TextContentRenderer}. See methods for default configuration. */ public static class Builder { From 320266c0af42f4672144adfc726ba3edad221a4e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 9 Jan 2024 16:50:07 +1100 Subject: [PATCH 003/345] Ignore .DS_Store --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index a156931f0..d998d8890 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ # Maven target/ + +# macOS +.DS_Store From 1ba567cd09bc3296b84d06dbfc4d3af9d5eff797 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 10 Jan 2024 14:36:09 +1100 Subject: [PATCH 004/345] Make SpecTestCase not extend RenderingTestCase That allows it to be used outside of HTML rendering test cases. With JUnit 5, it might not even need to be a base class anymore. --- .../integration/SourceSpanIntegrationTest.java | 2 +- .../integration/SpecIntegrationTest.java | 9 +++++---- .../java/org/commonmark/testutil/Asserts.java | 17 +++++++++++++++++ .../commonmark/testutil/RenderingTestCase.java | 14 +------------- .../org/commonmark/testutil/SpecTestCase.java | 9 +++------ .../java/org/commonmark/test/SpecCoreTest.java | 9 +++++++-- .../org/commonmark/test/SpecCrLfCoreTest.java | 11 +++++++++-- 7 files changed, 43 insertions(+), 28 deletions(-) create mode 100644 commonmark-test-util/src/main/java/org/commonmark/testutil/Asserts.java diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java index 6e51b0af5..b6fa4922a 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java @@ -5,7 +5,7 @@ import org.commonmark.testutil.example.Example; /** - * Spec and all extensions, with source spans enabed. + * Spec and all extensions, with source spans enabled. */ public class SourceSpanIntegrationTest extends SpecIntegrationTest { diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java index 13d5918b8..f434f65d2 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java @@ -12,10 +12,13 @@ import org.commonmark.parser.Parser; import org.commonmark.testutil.example.Example; import org.commonmark.testutil.SpecTestCase; +import org.junit.Assert; import org.junit.Test; import java.util.*; +import static org.commonmark.testutil.Asserts.assertRendering; + /** * Tests that the spec examples still render the same with all extensions enabled. */ @@ -39,17 +42,15 @@ public SpecIntegrationTest(Example example) { } @Test - @Override public void testHtmlRendering() { String expectedHtml = OVERRIDDEN_EXAMPLES.get(example.getSource()); if (expectedHtml != null) { - assertRendering(example.getSource(), expectedHtml); + assertRendering(example.getSource(), expectedHtml, render(example.getSource())); } else { - super.testHtmlRendering(); + assertRendering(example.getSource(), example.getHtml(), render(example.getSource())); } } - @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); } diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/Asserts.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/Asserts.java new file mode 100644 index 000000000..64124b129 --- /dev/null +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/Asserts.java @@ -0,0 +1,17 @@ +package org.commonmark.testutil; + +import static org.junit.Assert.assertEquals; + +public class Asserts { + public static void assertRendering(String source, String expectedRendering, String actualRendering) { + // include source for better assertion errors + String expected = showTabs(expectedRendering + "\n\n" + source); + String actual = showTabs(actualRendering + "\n\n" + source); + assertEquals(expected, actual); + } + + private static String showTabs(String s) { + // Tabs are shown as "rightwards arrow" for easier comparison + return s.replace("\t", "\u2192"); + } +} diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java index 682123494..b585f4604 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java @@ -1,22 +1,10 @@ package org.commonmark.testutil; -import static org.junit.Assert.assertEquals; - public abstract class RenderingTestCase { protected abstract String render(String source); protected void assertRendering(String source, String expectedResult) { - String renderedContent = render(source); - - // include source for better assertion errors - String expected = showTabs(expectedResult + "\n\n" + source); - String actual = showTabs(renderedContent + "\n\n" + source); - assertEquals(expected, actual); - } - - private static String showTabs(String s) { - // Tabs are shown as "rightwards arrow" for easier comparison - return s.replace("\t", "\u2192"); + Asserts.assertRendering(source, expectedResult, render(source)); } } diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java index 1c35b7c28..3be768682 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java @@ -10,8 +10,10 @@ import java.util.ArrayList; import java.util.List; +import static org.commonmark.testutil.Asserts.assertRendering; + @RunWith(Parameterized.class) -public abstract class SpecTestCase extends RenderingTestCase { +public abstract class SpecTestCase { protected final Example example; @@ -29,9 +31,4 @@ public static List data() { return data; } - @Test - public void testHtmlRendering() { - assertRendering(example.getSource(), example.getHtml()); - } - } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java index e4820f09c..8d284c6f9 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java @@ -9,6 +9,7 @@ import org.commonmark.testutil.example.Example; import org.junit.Test; +import static org.commonmark.testutil.Asserts.assertRendering; import static org.junit.Assert.fail; public class SpecCoreTest extends SpecTestCase { @@ -49,8 +50,12 @@ protected void visitChildren(Node parent) { }); } - @Override - protected String render(String source) { + @Test + public void testHtmlRendering() { + assertRendering(example.getSource(), example.getHtml(), render(example.getSource())); + } + + private String render(String source) { return RENDERER.render(PARSER.parse(source)); } } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java index 6424ab659..ab0103e5a 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java @@ -4,6 +4,9 @@ import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.SpecTestCase; import org.commonmark.testutil.example.Example; +import org.junit.Test; + +import static org.commonmark.testutil.Asserts.assertRendering; /** * Same as {@link SpecCoreTest} but converts line endings to Windows-style CR+LF endings before parsing. @@ -18,8 +21,12 @@ public SpecCrLfCoreTest(Example example) { super(example); } - @Override - protected String render(String source) { + @Test + public void testHtmlRendering() { + assertRendering(example.getSource(), example.getHtml(), render(example.getSource())); + } + + private String render(String source) { String windowsStyle = source.replace("\n", "\r\n"); return RENDERER.render(PARSER.parse(windowsStyle)); } From ec66829b7541b2539cc2126b8f4df4414e2607b8 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 16 Jan 2024 22:30:52 +1100 Subject: [PATCH 005/345] Add markerIndent and contentIndent to ListItem This information is required for a renderer producing Markdown. --- .../commonmark/internal/ListBlockParser.java | 2 +- .../commonmark/internal/ListItemParser.java | 4 +- .../java/org/commonmark/node/ListItem.java | 45 +++++++++++++ .../commonmark/test/ListBlockParserTest.java | 67 +++++++++++++++++++ .../test/java/org/commonmark/test/Nodes.java | 23 +++++++ 5 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index f6702518b..0ff644a47 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -217,7 +217,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } int newColumn = listData.contentColumn; - ListItemParser listItemParser = new ListItemParser(newColumn - state.getColumn()); + ListItemParser listItemParser = new ListItemParser(state.getIndent(), newColumn - state.getColumn()); // prepend the list block if needed if (!(matched instanceof ListBlockParser) || diff --git a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java index 6f03770b3..49722dff2 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java @@ -20,8 +20,10 @@ public class ListItemParser extends AbstractBlockParser { private boolean hadBlankLine; - public ListItemParser(int contentIndent) { + public ListItemParser(int markerIndent, int contentIndent) { this.contentIndent = contentIndent; + block.setMarkerIndent(markerIndent); + block.setContentIndent(contentIndent); } @Override diff --git a/commonmark/src/main/java/org/commonmark/node/ListItem.java b/commonmark/src/main/java/org/commonmark/node/ListItem.java index aa526be01..21f4e2b82 100644 --- a/commonmark/src/main/java/org/commonmark/node/ListItem.java +++ b/commonmark/src/main/java/org/commonmark/node/ListItem.java @@ -2,8 +2,53 @@ public class ListItem extends Block { + private int markerIndent; + private int contentIndent; + @Override public void accept(Visitor visitor) { visitor.visit(this); } + + /** + * Returns the indent of the marker such as "-" or "1." in columns (spaces or tab stop of 4). + *

+ * Some examples and their marker indent: + *

- Foo
+ * Marker indent: 0 + *
 - Foo
+ * Marker indent: 1 + *
  1. Foo
+ * Marker indent: 2 + */ + public int getMarkerIndent() { + return markerIndent; + } + + public void setMarkerIndent(int markerIndent) { + this.markerIndent = markerIndent; + } + + /** + * Returns the indent of the content in columns (spaces or tab stop of 4). The content indent is counted from the + * beginning of the line and includes the marker on the first line. + *

+ * Some examples and their content indent: + *

- Foo
+ * Content indent: 2 + *
 - Foo
+ * Content indent: 3 + *
  1. Foo
+ * Content indent: 5 + *

+ * Note that subsequent lines in the same list item need to be indented by at least the content indent to be counted + * as part of the list item. + */ + public int getContentIndent() { + return contentIndent; + } + + public void setContentIndent(int contentIndent) { + this.contentIndent = contentIndent; + } } diff --git a/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java b/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java new file mode 100644 index 000000000..a8a03fb74 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java @@ -0,0 +1,67 @@ +package org.commonmark.test; + +import org.commonmark.node.ListItem; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class ListBlockParserTest { + + private static final Parser PARSER = Parser.builder().build(); + + @Test + public void testBulletListIndents() { + assertListItemIndents("* foo", 0, 2); + assertListItemIndents(" * foo", 1, 3); + assertListItemIndents(" * foo", 2, 4); + assertListItemIndents(" * foo", 3, 5); + + assertListItemIndents("* foo", 0, 3); + assertListItemIndents("* foo", 0, 4); + assertListItemIndents("* foo", 0, 5); + assertListItemIndents(" * foo", 1, 4); + assertListItemIndents(" * foo", 3, 8); + + // The indent is relative to any containing blocks + assertListItemIndents("> * foo", 0, 2); + assertListItemIndents("> * foo", 1, 3); + assertListItemIndents("> * foo", 1, 4); + + // Tab counts as 3 spaces here (to the next tab stop column of 4) -> content indent is 1+3 + assertListItemIndents("*\tfoo", 0, 4); + + // Empty list, content indent is expected to be 2 + assertListItemIndents("-\n", 0, 2); + } + + @Test + public void testOrderedListIndents() { + assertListItemIndents("1. foo", 0, 3); + assertListItemIndents(" 1. foo", 1, 4); + assertListItemIndents(" 1. foo", 2, 5); + assertListItemIndents(" 1. foo", 3, 6); + + assertListItemIndents("1. foo", 0, 4); + assertListItemIndents("1. foo", 0, 5); + assertListItemIndents("1. foo", 0, 6); + assertListItemIndents(" 1. foo", 1, 5); + assertListItemIndents(" 1. foo", 2, 8); + + assertListItemIndents("> 1. foo", 0, 3); + assertListItemIndents("> 1. foo", 1, 4); + assertListItemIndents("> 1. foo", 1, 5); + + assertListItemIndents("1.\tfoo", 0, 4); + } + + private void assertListItemIndents(String input, int expectedMarkerIndent, int expectedContentIndent) { + Node doc = PARSER.parse(input); + ListItem listItem = Nodes.find(doc, ListItem.class); + assertNotNull(listItem); + assertEquals(expectedMarkerIndent, listItem.getMarkerIndent()); + assertEquals(expectedContentIndent, listItem.getContentIndent()); + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/Nodes.java b/commonmark/src/test/java/org/commonmark/test/Nodes.java index bbc019a6a..25ce75836 100644 --- a/commonmark/src/test/java/org/commonmark/test/Nodes.java +++ b/commonmark/src/test/java/org/commonmark/test/Nodes.java @@ -14,4 +14,27 @@ public static List getChildren(Node parent) { } return children; } + + /** + * Recursively try to find a node with the given type within the children of the specified node. + * + * @param parent The node to get children from (node itself will not be checked) + * @param nodeClass The type of node to find + */ + public static T find(Node parent, Class nodeClass) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + if (nodeClass.isInstance(node)) { + //noinspection unchecked + return (T) node; + } + T result = find(node, nodeClass); + if (result != null) { + return result; + } + node = next; + } + return null; + } } From 9ec3935d7fc4982d9c407d89d6810a13fa3a8a22 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 9 Jan 2024 16:53:44 +1100 Subject: [PATCH 006/345] Beginnings of a MarkdownRenderer Structure mostly copied from TextContentRenderer (for extension related bits). Started with leaf blocks (in spec order), but probably good to do some inlines too before going too far down all the blocks. Also, container blocks might be the most interesting. --- .../markdown/CoreMarkdownNodeRenderer.java | 152 ++++++++++++++++++ .../markdown/MarkdownNodeRendererContext.java | 19 +++ .../markdown/MarkdownNodeRendererFactory.java | 17 ++ .../renderer/markdown/MarkdownRenderer.java | 134 +++++++++++++++ .../renderer/markdown/MarkdownWriter.java | 52 ++++++ .../markdown/MarkdownRendererTest.java | 50 ++++++ 6 files changed, 424 insertions(+) create mode 100644 commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java create mode 100644 commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java create mode 100644 commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java create mode 100644 commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java create mode 100644 commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java create mode 100644 commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java new file mode 100644 index 000000000..ee4fbbe95 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -0,0 +1,152 @@ +package org.commonmark.renderer.markdown; + +import org.commonmark.node.*; +import org.commonmark.renderer.NodeRenderer; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * The node renderer that renders all the core nodes (comes last in the order of node renderers). + */ +public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer { + + protected final MarkdownNodeRendererContext context; + private final MarkdownWriter writer; + + public CoreMarkdownNodeRenderer(MarkdownNodeRendererContext context) { + this.context = context; + this.writer = context.getWriter(); + } + + @Override + public Set> getNodeTypes() { + return new HashSet<>(Arrays.asList( + Document.class, + Heading.class, + Paragraph.class, + BlockQuote.class, + BulletList.class, + FencedCodeBlock.class, + HtmlBlock.class, + ThematicBreak.class, + IndentedCodeBlock.class, + Link.class, + ListItem.class, + OrderedList.class, + Image.class, + Emphasis.class, + StrongEmphasis.class, + Text.class, + Code.class, + HtmlInline.class, + SoftLineBreak.class, + HardLineBreak.class + )); + } + + @Override + public void render(Node node) { + node.accept(this); + } + + @Override + public void visit(Document document) { + // No rendering itself + visitChildren(document); + } + + @Override + public void visit(BlockQuote blockQuote) { + } + + @Override + public void visit(BulletList bulletList) { + } + + @Override + public void visit(Code code) { + } + + @Override + public void visit(FencedCodeBlock fencedCodeBlock) { + } + + @Override + public void visit(HardLineBreak hardLineBreak) { + } + + @Override + public void visit(Heading heading) { + for (int i = 0; i < heading.getLevel(); i++) { + writer.write('#'); + } + writer.write(' '); + visitChildren(heading); + writer.block(); + } + + @Override + public void visit(ThematicBreak thematicBreak) { + writer.write("***"); + writer.block(); + } + + @Override + public void visit(HtmlInline htmlInline) { + } + + @Override + public void visit(HtmlBlock htmlBlock) { + } + + @Override + public void visit(Image image) { + } + + @Override + public void visit(IndentedCodeBlock indentedCodeBlock) { + } + + @Override + public void visit(Link link) { + } + + @Override + public void visit(ListItem listItem) { + } + + @Override + public void visit(OrderedList orderedList) { + } + + @Override + public void visit(Paragraph paragraph) { + visitChildren(paragraph); + writer.block(); + } + + @Override + public void visit(SoftLineBreak softLineBreak) { + } + + @Override + public void visit(Text text) { + writeText(text.getLiteral()); + } + + @Override + protected void visitChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } + + private void writeText(String text) { + writer.write(text); + } +} diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java new file mode 100644 index 000000000..8fe0f73d5 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java @@ -0,0 +1,19 @@ +package org.commonmark.renderer.markdown; + +import org.commonmark.node.Node; + +public interface MarkdownNodeRendererContext { + + /** + * @return the writer to use + */ + MarkdownWriter getWriter(); + + /** + * Render the specified node and its children using the configured renderers. This should be used to render child + * nodes; be careful not to pass the node that is being rendered, that would result in an endless loop. + * + * @param node the node to render + */ + void render(Node node); +} diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java new file mode 100644 index 000000000..7b3134277 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java @@ -0,0 +1,17 @@ +package org.commonmark.renderer.markdown; + +import org.commonmark.renderer.NodeRenderer; + +/** + * Factory for instantiating new node renderers ƒor rendering. + */ +public interface MarkdownNodeRendererFactory { + + /** + * Create a new node renderer for the specified rendering context. + * + * @param context the context for rendering (normally passed on to the node renderer) + * @return a node renderer + */ + NodeRenderer create(MarkdownNodeRendererContext context); +} diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java new file mode 100644 index 000000000..6466b97a2 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java @@ -0,0 +1,134 @@ +package org.commonmark.renderer.markdown; + +import org.commonmark.Extension; +import org.commonmark.internal.renderer.NodeRendererMap; +import org.commonmark.node.Node; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.Renderer; + +import java.util.ArrayList; +import java.util.List; + +/** + * Renders nodes to CommonMark Markdown. + *

+ * Note that it does not currently attempt to preserve the exact syntax of the original input Markdown (if any): + *

    + *
  • Headings are always output as ATX headings for simplicity
  • + *
+ */ +public class MarkdownRenderer implements Renderer { + + private final List nodeRendererFactories; + + private MarkdownRenderer(Builder builder) { + this.nodeRendererFactories = new ArrayList<>(builder.nodeRendererFactories.size() + 1); + this.nodeRendererFactories.addAll(builder.nodeRendererFactories); + // Add as last. This means clients can override the rendering of core nodes if they want. + this.nodeRendererFactories.add(new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new CoreMarkdownNodeRenderer(context); + } + }); + } + + /** + * Create a new builder for configuring a {@link MarkdownRenderer}. + * + * @return a builder + */ + public static Builder builder() { + return new Builder(); + } + + @Override + public void render(Node node, Appendable output) { + RendererContext context = new RendererContext(new MarkdownWriter(output)); + context.render(node); + } + + @Override + public String render(Node node) { + StringBuilder sb = new StringBuilder(); + render(node, sb); + return sb.toString(); + } + + /** + * Builder for configuring a {@link MarkdownRenderer}. See methods for default configuration. + */ + public static class Builder { + + private List nodeRendererFactories = new ArrayList<>(); + + /** + * @return the configured {@link MarkdownRenderer} + */ + public MarkdownRenderer build() { + return new MarkdownRenderer(this); + } + + /** + * Add a factory for instantiating a node renderer (done when rendering). This allows to override the rendering + * of node types or define rendering for custom node types. + *

+ * If multiple node renderers for the same node type are created, the one from the factory that was added first + * "wins". (This is how the rendering for core node types can be overridden; the default rendering comes last.) + * + * @param nodeRendererFactory the factory for creating a node renderer + * @return {@code this} + */ + public Builder nodeRendererFactory(MarkdownNodeRendererFactory nodeRendererFactory) { + this.nodeRendererFactories.add(nodeRendererFactory); + return this; + } + + /** + * @param extensions extensions to use on this renderer + * @return {@code this} + */ + public Builder extensions(Iterable extensions) { + for (Extension extension : extensions) { + if (extension instanceof MarkdownRendererExtension) { + MarkdownRendererExtension markdownRendererExtension = (MarkdownRendererExtension) extension; + markdownRendererExtension.extend(this); + } + } + return this; + } + } + + /** + * Extension for {@link MarkdownRenderer}. + */ + public interface MarkdownRendererExtension extends Extension { + void extend(Builder rendererBuilder); + } + + private class RendererContext implements MarkdownNodeRendererContext { + private final MarkdownWriter writer; + private final NodeRendererMap nodeRendererMap = new NodeRendererMap(); + + private RendererContext(MarkdownWriter writer) { + this.writer = writer; + + // The first node renderer for a node type "wins". + for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) { + MarkdownNodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i); + NodeRenderer nodeRenderer = nodeRendererFactory.create(this); + nodeRendererMap.add(nodeRenderer); + } + } + + @Override + public MarkdownWriter getWriter() { + return writer; + } + + @Override + public void render(Node node) { + nodeRendererMap.render(node); + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java new file mode 100644 index 000000000..1377d86f9 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -0,0 +1,52 @@ +package org.commonmark.renderer.markdown; + +import java.io.IOException; + +public class MarkdownWriter { + + private final Appendable buffer; + + private boolean prependLine = false; + + public MarkdownWriter(Appendable out) { + buffer = out; + } + + public void block() { + append('\n'); + prependLine = true; + } + + public void write(String s) { + append(s); + } + + public void write(char c) { + append(c); + } + + private void append(String s) { + try { + appendLineIfNeeded(); + buffer.append(s); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void append(char c) { + try { + appendLineIfNeeded(); + buffer.append(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void appendLineIfNeeded() throws IOException { + if (prependLine) { + buffer.append('\n'); + prependLine = false; + } + } +} diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java new file mode 100644 index 000000000..297dfa206 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -0,0 +1,50 @@ +package org.commonmark.renderer.markdown; + +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class MarkdownRendererTest { + + @Test + public void testThematicBreaks() { + assertRoundTrip("***\n"); + // TODO: spec: If you want a thematic break in a list item, use a different bullet: + + assertRoundTrip("***\n\nfoo\n"); + } + + @Test + public void testHeadings() { + // Type of heading is currently not preserved + assertRoundTrip("# foo\n"); + assertRoundTrip("## foo\n"); + assertRoundTrip("### foo\n"); + assertRoundTrip("#### foo\n"); + assertRoundTrip("##### foo\n"); + assertRoundTrip("###### foo\n"); + + assertRoundTrip("# foo\n\nbar\n"); + } + + @Test + public void testParagraphs() { + assertRoundTrip("foo\n"); + assertRoundTrip("foo\n\nbar\n"); + } + + private Node parse(String source) { + return Parser.builder().build().parse(source); + } + + private String render(String source) { + return MarkdownRenderer.builder().build().render(parse(source)); + } + + private void assertRoundTrip(String input) { + String rendered = render(input); + assertEquals(input, rendered); + } +} From b25b7ead8fd8f3066b1361ff8faf2a679021a43b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 10 Jan 2024 12:04:12 +1100 Subject: [PATCH 007/345] Code spans --- .../markdown/CoreMarkdownNodeRenderer.java | 34 +++++++++++++++++++ .../markdown/MarkdownRendererTest.java | 9 +++++ 2 files changed, 43 insertions(+) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index ee4fbbe95..9addf72fa 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -1,5 +1,6 @@ package org.commonmark.renderer.markdown; +import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; @@ -67,6 +68,24 @@ public void visit(BulletList bulletList) { @Override public void visit(Code code) { + String literal = code.getLiteral(); + // If the literal includes backticks, we can surround them by using one more backtick. + int backticks = findMaxRunLength('`', literal); + for (int i = 0; i < backticks + 1; i++) { + writer.write('`'); + } + // If the literal starts or ends with a backtick, surround it with a single space. + boolean addSpace = literal.startsWith("`") || literal.endsWith("`"); + if (addSpace) { + writer.write(' '); + } + writer.write(literal); + if (addSpace) { + writer.write(' '); + } + for (int i = 0; i < backticks + 1; i++) { + writer.write('`'); + } } @Override @@ -146,6 +165,21 @@ protected void visitChildren(Node parent) { } } + private static int findMaxRunLength(char c, CharSequence s) { + int backticks = 0; + int start = 0; + while (start < s.length()) { + int index = Parsing.find(c, s, start); + if (index != -1) { + start = Parsing.skip(c, s, index + 1, s.length()); + backticks = Math.max(backticks, start - index); + } else { + break; + } + } + return backticks; + } + private void writeText(String text) { writer.write(text); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 297dfa206..caa4bdf88 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -35,6 +35,15 @@ public void testParagraphs() { assertRoundTrip("foo\n\nbar\n"); } + @Test + public void testCodeSpans() { + assertRoundTrip("`foo`\n"); + assertRoundTrip("``foo ` bar``\n"); + assertRoundTrip("```foo `` ` bar```\n"); + + assertRoundTrip("`` `foo ``\n"); + } + private Node parse(String source) { return Parser.builder().build().parse(source); } From aa7ac8b5183a8a3715de0eebfe49feec72c98306 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 10 Jan 2024 15:43:49 +1100 Subject: [PATCH 008/345] Add test that renders the spec examples through Markdown --- .../markdown/SpecMarkdownRendererTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java new file mode 100644 index 000000000..87d7507c6 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -0,0 +1,56 @@ +package org.commonmark.renderer.markdown; + +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.TestResources; +import org.commonmark.testutil.example.Example; +import org.commonmark.testutil.example.ExampleReader; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertTrue; + +/** + * Tests Markdown rendering using the examples in the spec like this: + *

    + *
  1. Parses the source to an AST and then renders it back to Markdown
  2. + *
  3. Parses that to an AST and then renders it to HTML
  4. + *
  5. Compares that HTML to the expected HTML of the example: + * If it's the same, then the expected elements were preserved in the Markdown rendering
  6. + *
+ */ +public class SpecMarkdownRendererTest { + + public static final MarkdownRenderer MARKDOWN_RENDERER = MarkdownRenderer.builder().build(); + public static final HtmlRenderer HTML_RENDERER = HtmlRenderer.builder().build(); + + @Test + public void testCoverage() { + List examples = ExampleReader.readExamples(TestResources.getSpec()); + int passed = 0; + for (Example example : examples) { + String markdown = renderMarkdown(example.getSource()); + String rendered = renderHtml(markdown); + if (rendered.equals(example.getHtml())) { + passed++; + } + } + + int expectedPassed = 151; + assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passed, passed >= expectedPassed); + } + + private Node parse(String source) { + return Parser.builder().build().parse(source); + } + + private String renderMarkdown(String source) { + return MARKDOWN_RENDERER.render(parse(source)); + } + + private String renderHtml(String source) { + return HTML_RENDERER.render(parse(source)); + } +} From bd8fa6a348f74e6a3c6d0fb7762d23bc030caeab Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 10 Jan 2024 15:57:58 +1100 Subject: [PATCH 009/345] Count how many examples pass/fail --- .../commonmark/testutil/example/Example.java | 4 +++ .../markdown/SpecMarkdownRendererTest.java | 33 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/example/Example.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/Example.java index 417a66097..11e87d0aa 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/example/Example.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/Example.java @@ -30,6 +30,10 @@ public String getHtml() { return html; } + public String getSection() { + return section; + } + @Override public String toString() { return "File \"" + filename + "\" section \"" + section + "\" example " + exampleNumber; diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 87d7507c6..06791e20f 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -8,7 +8,10 @@ import org.commonmark.testutil.example.ExampleReader; import org.junit.Test; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import static org.junit.Assert.assertTrue; @@ -29,17 +32,41 @@ public class SpecMarkdownRendererTest { @Test public void testCoverage() { List examples = ExampleReader.readExamples(TestResources.getSpec()); - int passed = 0; + List passes = new ArrayList<>(); + List fails = new ArrayList<>(); for (Example example : examples) { String markdown = renderMarkdown(example.getSource()); String rendered = renderHtml(markdown); if (rendered.equals(example.getHtml())) { - passed++; + passes.add(example); + } else { + fails.add(example); } } + System.out.println("Failed examples by section:"); + printCountsBySection(fails); + System.out.println(); + + System.out.println("Passed examples by section:"); + printCountsBySection(passes); + int expectedPassed = 151; - assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passed, passed >= expectedPassed); + assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); + } + + private static void printCountsBySection(List examples) { + Map bySection = new LinkedHashMap<>(); + for (Example example : examples) { + Integer count = bySection.get(example.getSection()); + if (count == null) { + count = 0; + } + bySection.put(example.getSection(), count + 1); + } + for (Map.Entry entry : bySection.entrySet()) { + System.out.println(entry.getKey() + ": " + entry.getValue()); + } } private Node parse(String source) { From 25abdad8665e0652c1f98cf4add17946294b5bd8 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 10 Jan 2024 16:21:44 +1100 Subject: [PATCH 010/345] Emphasis and strong emphasis --- .../markdown/CoreMarkdownNodeRenderer.java | 16 ++++++++++++++++ .../renderer/markdown/MarkdownWriter.java | 12 ++++++++++++ .../markdown/MarkdownRendererTest.java | 18 ++++++++++++++++++ .../markdown/SpecMarkdownRendererTest.java | 10 +++++----- 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 9addf72fa..c968da850 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -88,6 +88,15 @@ public void visit(Code code) { } } + @Override + public void visit(Emphasis emphasis) { + // When emphasis is nested, a different delimiter needs to be used + char delimiter = writer.getLastChar() == '*' ? '_' : '*'; + writer.write(delimiter); + super.visit(emphasis); + writer.write(delimiter); + } + @Override public void visit(FencedCodeBlock fencedCodeBlock) { } @@ -150,6 +159,13 @@ public void visit(Paragraph paragraph) { public void visit(SoftLineBreak softLineBreak) { } + @Override + public void visit(StrongEmphasis strongEmphasis) { + writer.write("**"); + super.visit(strongEmphasis); + writer.write("**"); + } + @Override public void visit(Text text) { writeText(text.getLiteral()); diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index 1377d86f9..fd79ce6af 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -7,11 +7,16 @@ public class MarkdownWriter { private final Appendable buffer; private boolean prependLine = false; + private char lastChar; public MarkdownWriter(Appendable out) { buffer = out; } + public char getLastChar() { + return lastChar; + } + public void block() { append('\n'); prependLine = true; @@ -32,6 +37,11 @@ private void append(String s) { } catch (IOException e) { throw new RuntimeException(e); } + + int length = s.length(); + if (length != 0) { + lastChar = s.charAt(length - 1); + } } private void append(char c) { @@ -41,6 +51,8 @@ private void append(char c) { } catch (IOException e) { throw new RuntimeException(e); } + + lastChar = c; } private void appendLineIfNeeded() throws IOException { diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index caa4bdf88..0a2983a90 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -44,6 +44,24 @@ public void testCodeSpans() { assertRoundTrip("`` `foo ``\n"); } + @Test + public void testEmphasis() { + assertRoundTrip("*foo*\n"); + assertRoundTrip("foo*bar*\n"); + // When nesting, a different delimiter needs to be used + assertRoundTrip("*_foo_*\n"); + assertRoundTrip("*_*foo*_*\n"); + + // Not emphasis (needs * inside words) + assertRoundTrip("foo_bar_\n"); + } + + @Test + public void testStrongEmphasis() { + assertRoundTrip("**foo**\n"); + assertRoundTrip("foo**bar**\n"); + } + private Node parse(String source) { return Parser.builder().build().parse(source); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 06791e20f..c94465e7a 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -44,14 +44,14 @@ public void testCoverage() { } } - System.out.println("Failed examples by section:"); - printCountsBySection(fails); + System.out.println("Passed examples by section (total " + passes.size() + "):"); + printCountsBySection(passes); System.out.println(); - System.out.println("Passed examples by section:"); - printCountsBySection(passes); + System.out.println("Failed examples by section (total " + fails.size() + "):"); + printCountsBySection(fails); - int expectedPassed = 151; + int expectedPassed = 226; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 3e99b4886e4aac77856d05aaaf56e3868cb05c78 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 10 Jan 2024 16:27:36 +1100 Subject: [PATCH 011/345] Line breaks --- .../renderer/markdown/CoreMarkdownNodeRenderer.java | 3 +++ .../commonmark/renderer/markdown/MarkdownWriter.java | 4 ++++ .../renderer/markdown/MarkdownRendererTest.java | 10 ++++++++++ .../renderer/markdown/SpecMarkdownRendererTest.java | 2 +- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index c968da850..1f2943951 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -103,6 +103,8 @@ public void visit(FencedCodeBlock fencedCodeBlock) { @Override public void visit(HardLineBreak hardLineBreak) { + writer.write(" "); + writer.line(); } @Override @@ -157,6 +159,7 @@ public void visit(Paragraph paragraph) { @Override public void visit(SoftLineBreak softLineBreak) { + writer.line(); } @Override diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index fd79ce6af..5753026a8 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -22,6 +22,10 @@ public void block() { prependLine = true; } + public void line() { + append('\n'); + } + public void write(String s) { append(s); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 0a2983a90..619830aec 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -62,6 +62,16 @@ public void testStrongEmphasis() { assertRoundTrip("foo**bar**\n"); } + @Test + public void testHardLineBreaks() { + assertRoundTrip("foo \nbar\n"); + } + + @Test + public void testSoftLineBreaks() { + assertRoundTrip("foo\nbar\n"); + } + private Node parse(String source) { return Parser.builder().build().parse(source); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index c94465e7a..11a077b13 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -51,7 +51,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 226; + int expectedPassed = 263; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 43006b2d572e30b1e054352fcf10600a297fc8cc Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 10 Jan 2024 17:37:45 +1100 Subject: [PATCH 012/345] Links and images --- .../markdown/CoreMarkdownNodeRenderer.java | 41 +++++++++++++++++++ .../renderer/markdown/MarkdownRenderer.java | 2 + .../renderer/markdown/MarkdownWriter.java | 22 ++++++++++ .../markdown/MarkdownRendererTest.java | 24 +++++++++++ .../markdown/SpecMarkdownRendererTest.java | 4 +- 5 files changed, 91 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 1f2943951..376841ec7 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -1,5 +1,7 @@ package org.commonmark.renderer.markdown; +import org.commonmark.internal.util.AsciiMatcher; +import org.commonmark.internal.util.CharMatcher; import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; @@ -13,6 +15,13 @@ */ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer { + private final CharMatcher linkDestinationNeedsAngleBrackets = + AsciiMatcher.builder().c(' ').c('(').c(')').c('<').c('>').c('\\').build(); + private final CharMatcher linkDestinationEscapeInAngleBrackets = + AsciiMatcher.builder().c('<').c('>').build(); + private final CharMatcher linkTitleEscapeInQuotes = + AsciiMatcher.builder().c('"').build(); + protected final MarkdownNodeRendererContext context; private final MarkdownWriter writer; @@ -133,6 +142,7 @@ public void visit(HtmlBlock htmlBlock) { @Override public void visit(Image image) { + writeLinkLike(image.getTitle(), image.getDestination(), image, "!["); } @Override @@ -141,6 +151,7 @@ public void visit(IndentedCodeBlock indentedCodeBlock) { @Override public void visit(Link link) { + writeLinkLike(link.getTitle(), link.getDestination(), link, "["); } @Override @@ -199,7 +210,37 @@ private static int findMaxRunLength(char c, CharSequence s) { return backticks; } + private static boolean contains(String s, CharMatcher charMatcher) { + for (int i = 0; i < s.length(); i++) { + if (charMatcher.matches(s.charAt(i))) { + return true; + } + } + return false; + } + private void writeText(String text) { writer.write(text); } + + private void writeLinkLike(String title, String destination, Node node, String opener) { + writer.write(opener); + visitChildren(node); + writer.write(']'); + writer.write('('); + if (contains(destination, linkDestinationNeedsAngleBrackets)) { + writer.write('<'); + writer.writeEscaped(destination, linkDestinationEscapeInAngleBrackets); + writer.write('>'); + } else { + writer.write(destination); + } + if (title != null) { + writer.write(' '); + writer.write('"'); + writer.writeEscaped(title, linkTitleEscapeInQuotes); + writer.write('"'); + } + writer.write(')'); + } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java index 6466b97a2..4dc8dbff9 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java @@ -15,6 +15,8 @@ * Note that it does not currently attempt to preserve the exact syntax of the original input Markdown (if any): *
    *
  • Headings are always output as ATX headings for simplicity
  • + *
  • Escaping might be over-eager, e.g. a plain {@code *} might be escaped + * even though it doesn't need to be in that particular context
  • *
*/ public class MarkdownRenderer implements Renderer { diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index 5753026a8..fd88b73ef 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -1,5 +1,7 @@ package org.commonmark.renderer.markdown; +import org.commonmark.internal.util.CharMatcher; + import java.io.IOException; public class MarkdownWriter { @@ -34,6 +36,26 @@ public void write(char c) { append(c); } + public void writeEscaped(String s, CharMatcher escape) { + if (s.isEmpty()) { + return; + } + try { + appendLineIfNeeded(); + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + if (ch == '\\' || escape.matches(ch)) { + buffer.append('\\'); + } + buffer.append(ch); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + lastChar = s.charAt(s.length() - 1); + } + private void append(String s) { try { appendLineIfNeeded(); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 619830aec..263631f1f 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -62,6 +62,30 @@ public void testStrongEmphasis() { assertRoundTrip("foo**bar**\n"); } + @Test + public void testLinks() { + assertRoundTrip("[link](/uri)\n"); + assertRoundTrip("[link](/uri \"title\")\n"); + assertRoundTrip("[link]()\n"); + assertRoundTrip("[a]()\n"); + assertRoundTrip("[a]()\n"); + assertRoundTrip("[a](c>)\n"); + assertRoundTrip("[a](c>)\n"); + assertRoundTrip("[a](/uri \"foo \\\" bar\")\n"); + } + + @Test + public void testImages() { + assertRoundTrip("![link](/uri)\n"); + assertRoundTrip("![link](/uri \"title\")\n"); + assertRoundTrip("![link]()\n"); + assertRoundTrip("![a]()\n"); + assertRoundTrip("![a]()\n"); + assertRoundTrip("![a](c>)\n"); + assertRoundTrip("![a](c>)\n"); + assertRoundTrip("![a](/uri \"foo \\\" bar\")\n"); + } + @Test public void testHardLineBreaks() { assertRoundTrip("foo \nbar\n"); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 11a077b13..c05aef846 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -51,7 +51,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 263; + int expectedPassed = 372; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } @@ -65,7 +65,7 @@ private static void printCountsBySection(List examples) { bySection.put(example.getSection(), count + 1); } for (Map.Entry entry : bySection.entrySet()) { - System.out.println(entry.getKey() + ": " + entry.getValue()); + System.out.println(entry.getValue() + ": " + entry.getKey()); } } From 88c767a67040c2f2308f1745d3f0f3592641c663 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 10 Jan 2024 17:44:11 +1100 Subject: [PATCH 013/345] HTML blocks and inlines --- .../renderer/markdown/CoreMarkdownNodeRenderer.java | 3 +++ .../renderer/markdown/MarkdownRendererTest.java | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 376841ec7..77b6b0c17 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -134,10 +134,13 @@ public void visit(ThematicBreak thematicBreak) { @Override public void visit(HtmlInline htmlInline) { + writer.write(htmlInline.getLiteral()); } @Override public void visit(HtmlBlock htmlBlock) { + writer.write(htmlBlock.getLiteral()); + writer.block(); } @Override diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 263631f1f..e9c300cbb 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -29,6 +29,11 @@ public void testHeadings() { assertRoundTrip("# foo\n\nbar\n"); } + @Test + public void testHtmlBlocks() { + assertRoundTrip("
test
\n"); + } + @Test public void testParagraphs() { assertRoundTrip("foo\n"); @@ -86,6 +91,11 @@ public void testImages() { assertRoundTrip("![a](/uri \"foo \\\" bar\")\n"); } + @Test + public void testHtmlInline() { + assertRoundTrip("*foo*\n"); + } + @Test public void testHardLineBreaks() { assertRoundTrip("foo \nbar\n"); From e4ced9b071a32c2c14c614a0cbcd3c2ae4b4c9e6 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 10 Jan 2024 18:15:53 +1100 Subject: [PATCH 014/345] Block quotes --- .../markdown/CoreMarkdownNodeRenderer.java | 6 +++ .../renderer/markdown/MarkdownWriter.java | 41 ++++++++++++++----- .../markdown/MarkdownRendererTest.java | 14 +++++++ .../markdown/SpecMarkdownRendererTest.java | 2 +- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 77b6b0c17..04be11214 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -65,10 +65,16 @@ public void render(Node node) { public void visit(Document document) { // No rendering itself visitChildren(document); + writer.line(); } @Override public void visit(BlockQuote blockQuote) { + writer.write("> "); + writer.pushPrefix("> "); + visitChildren(blockQuote); + writer.popPrefix(); + writer.block(); } @Override diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index fd88b73ef..71c820559 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -3,13 +3,15 @@ import org.commonmark.internal.util.CharMatcher; import java.io.IOException; +import java.util.LinkedList; public class MarkdownWriter { private final Appendable buffer; - private boolean prependLine = false; + private boolean finishBlock = false; private char lastChar; + private final LinkedList prefixes = new LinkedList<>(); public MarkdownWriter(Appendable out) { buffer = out; @@ -20,19 +22,21 @@ public char getLastChar() { } public void block() { - append('\n'); - prependLine = true; + finishBlock = true; } public void line() { append('\n'); + writePrefixes(); } public void write(String s) { + finishBlockIfNeeded(); append(s); } public void write(char c) { + finishBlockIfNeeded(); append(c); } @@ -40,8 +44,8 @@ public void writeEscaped(String s, CharMatcher escape) { if (s.isEmpty()) { return; } + finishBlockIfNeeded(); try { - appendLineIfNeeded(); for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); if (ch == '\\' || escape.matches(ch)) { @@ -56,9 +60,16 @@ public void writeEscaped(String s, CharMatcher escape) { lastChar = s.charAt(s.length() - 1); } + public void pushPrefix(String prefix) { + prefixes.addLast(prefix); + } + + public void popPrefix() { + prefixes.removeLast(); + } + private void append(String s) { try { - appendLineIfNeeded(); buffer.append(s); } catch (IOException e) { throw new RuntimeException(e); @@ -72,7 +83,6 @@ private void append(String s) { private void append(char c) { try { - appendLineIfNeeded(); buffer.append(c); } catch (IOException e) { throw new RuntimeException(e); @@ -81,10 +91,21 @@ private void append(char c) { lastChar = c; } - private void appendLineIfNeeded() throws IOException { - if (prependLine) { - buffer.append('\n'); - prependLine = false; + private void finishBlockIfNeeded() { + if (finishBlock) { + finishBlock = false; + append('\n'); + writePrefixes(); + append('\n'); + writePrefixes(); + } + } + + private void writePrefixes() { + if (!prefixes.isEmpty()) { + for (String prefix : prefixes) { + append(prefix); + } } } } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index e9c300cbb..d2243d1b8 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -8,6 +8,8 @@ public class MarkdownRendererTest { + // Leaf blocks + @Test public void testThematicBreaks() { assertRoundTrip("***\n"); @@ -40,6 +42,18 @@ public void testParagraphs() { assertRoundTrip("foo\n\nbar\n"); } + // Container blocks + + @Test + public void testBlockQuotes() { + assertRoundTrip("> test\n"); + assertRoundTrip("> foo\n> bar\n"); + assertRoundTrip("> > foo\n> > bar\n"); + assertRoundTrip("> # Foo\n> \n> bar\n> baz\n"); + } + + // Inlines + @Test public void testCodeSpans() { assertRoundTrip("`foo`\n"); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index c05aef846..e40ec457a 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -51,7 +51,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 372; + int expectedPassed = 459; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From f640e94142873b69729e7bdee54810b5d3c8a82b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 10 Jan 2024 21:17:00 +1100 Subject: [PATCH 015/345] Indented code blocks --- .../markdown/CoreMarkdownNodeRenderer.java | 15 +++++++++++++++ .../renderer/markdown/MarkdownRendererTest.java | 7 +++++++ .../markdown/SpecMarkdownRendererTest.java | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 04be11214..ad0d276ae 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -156,6 +156,21 @@ public void visit(Image image) { @Override public void visit(IndentedCodeBlock indentedCodeBlock) { + String literal = indentedCodeBlock.getLiteral(); + String[] lines = literal.split("\n"); + // We need to respect line prefixes which is why we need to write it line by line (e.g. an indented code block + // within a block quote) + writer.pushPrefix(" "); + writer.write(" "); + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + writer.write(line); + if (i != lines.length - 1) { + writer.line(); + } + } + writer.popPrefix(); + writer.block(); } @Override diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index d2243d1b8..e8f0bd7ac 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -31,6 +31,13 @@ public void testHeadings() { assertRoundTrip("# foo\n\nbar\n"); } + @Test + public void testIndentedCodeBlocks() { + assertRoundTrip(" hi\n"); + assertRoundTrip(" hi\n code\n"); + assertRoundTrip("> hi\n> code\n"); + } + @Test public void testHtmlBlocks() { assertRoundTrip("
test
\n"); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index e40ec457a..225c34525 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -51,7 +51,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 459; + int expectedPassed = 481; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 31e36645fb3585dc500726c9ef775c2192f7edbb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 10 Jan 2024 21:29:03 +1100 Subject: [PATCH 016/345] Fenced code blocks --- .../markdown/CoreMarkdownNodeRenderer.java | 36 ++++++++++++++++++- .../markdown/MarkdownRendererTest.java | 8 +++++ .../markdown/SpecMarkdownRendererTest.java | 2 +- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index ad0d276ae..cec810e34 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -114,6 +114,32 @@ public void visit(Emphasis emphasis) { @Override public void visit(FencedCodeBlock fencedCodeBlock) { + String literal = fencedCodeBlock.getLiteral(); + String fence = repeat(String.valueOf(fencedCodeBlock.getFenceChar()), fencedCodeBlock.getFenceLength()); + int indent = fencedCodeBlock.getFenceIndent(); + + if (indent > 0) { + String indentPrefix = repeat(" ", indent); + writer.write(indentPrefix); + writer.pushPrefix(indentPrefix); + } + + writer.write(fence); + if (fencedCodeBlock.getInfo() != null) { + writer.write(fencedCodeBlock.getInfo()); + } + writer.line(); + String[] lines = literal.split("\n"); + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + writer.write(line); + writer.line(); + } + writer.write(fence); + if (indent > 0) { + writer.popPrefix(); + } + writer.block(); } @Override @@ -160,8 +186,8 @@ public void visit(IndentedCodeBlock indentedCodeBlock) { String[] lines = literal.split("\n"); // We need to respect line prefixes which is why we need to write it line by line (e.g. an indented code block // within a block quote) - writer.pushPrefix(" "); writer.write(" "); + writer.pushPrefix(" "); for (int i = 0; i < lines.length; i++) { String line = lines[i]; writer.write(line); @@ -243,6 +269,14 @@ private static boolean contains(String s, CharMatcher charMatcher) { return false; } + private static String repeat(String s, int count) { + StringBuilder sb = new StringBuilder(s.length() * count); + for (int i = 0; i < count; i++) { + sb.append(s); + } + return sb.toString(); + } + private void writeText(String text) { writer.write(text); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index e8f0bd7ac..04b6cc262 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -38,6 +38,14 @@ public void testIndentedCodeBlocks() { assertRoundTrip("> hi\n> code\n"); } + @Test + public void testFencedCodeBlocks() { + assertRoundTrip("```\ntest\n```\n"); + assertRoundTrip("~~~~\ntest\n~~~~\n"); + assertRoundTrip("```info\ntest\n```\n"); + assertRoundTrip(" ```\n test\n ```\n"); + } + @Test public void testHtmlBlocks() { assertRoundTrip("
test
\n"); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 225c34525..703b52bfc 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -51,7 +51,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 481; + int expectedPassed = 507; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From bbde769f5723ab2b2ae5a0016670e82b1e608196 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jan 2024 16:08:42 +1100 Subject: [PATCH 017/345] Lists (not tight lists yet) --- .../markdown/CoreMarkdownNodeRenderer.java | 67 ++++++++++++++++++- .../markdown/MarkdownRendererTest.java | 19 ++++++ .../markdown/SpecMarkdownRendererTest.java | 2 +- 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index cec810e34..77bd855ea 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -24,6 +24,11 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen protected final MarkdownNodeRendererContext context; private final MarkdownWriter writer; + /** + * If we're currently within a {@link BulletList} or {@link OrderedList}, this keeps the context of that list. + * It has a parent field so that it can represent a stack (for nested lists). + */ + private ListHolder listHolder; public CoreMarkdownNodeRenderer(MarkdownNodeRendererContext context) { this.context = context; @@ -79,6 +84,9 @@ public void visit(BlockQuote blockQuote) { @Override public void visit(BulletList bulletList) { + listHolder = new BulletListHolder(listHolder, bulletList); + visitChildren(bulletList); + listHolder = listHolder.parent; } @Override @@ -130,8 +138,7 @@ public void visit(FencedCodeBlock fencedCodeBlock) { } writer.line(); String[] lines = literal.split("\n"); - for (int i = 0; i < lines.length; i++) { - String line = lines[i]; + for (String line : lines) { writer.write(line); writer.line(); } @@ -206,16 +213,42 @@ public void visit(Link link) { @Override public void visit(ListItem listItem) { + boolean pushedPrefix = false; + if (listHolder instanceof BulletListHolder) { + BulletListHolder bulletListHolder = (BulletListHolder) listHolder; + String prefix = bulletListHolder.bulletMarker + " "; + writer.write(prefix); + writer.pushPrefix(repeat(" ", prefix.length())); + pushedPrefix = true; + } else if (listHolder instanceof OrderedListHolder) { + OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; + String prefix = String.valueOf(orderedListHolder.number) + orderedListHolder.delimiter + " "; + orderedListHolder.number++; + writer.write(prefix); + writer.pushPrefix(repeat(" ", prefix.length())); + pushedPrefix = true; + } + visitChildren(listItem); + if (pushedPrefix) { + writer.popPrefix(); + } } @Override public void visit(OrderedList orderedList) { + listHolder = new OrderedListHolder(listHolder, orderedList); + visitChildren(orderedList); + listHolder = listHolder.parent; } @Override public void visit(Paragraph paragraph) { visitChildren(paragraph); - writer.block(); + if (paragraph.getParent() instanceof ListItem) { + writer.block(); + } else { + writer.block(); + } } @Override @@ -301,4 +334,32 @@ private void writeLinkLike(String title, String destination, Node node, String o } writer.write(')'); } + + private static class ListHolder { + final ListHolder parent; + + protected ListHolder(ListHolder parent) { + this.parent = parent; + } + } + + private static class BulletListHolder extends ListHolder { + final char bulletMarker; + + public BulletListHolder(ListHolder parent, BulletList bulletList) { + super(parent); + this.bulletMarker = bulletList.getBulletMarker(); + } + } + + private static class OrderedListHolder extends ListHolder { + final char delimiter; + private int number; + + protected OrderedListHolder(ListHolder parent, OrderedList orderedList) { + super(parent); + delimiter = orderedList.getDelimiter(); + number = orderedList.getStartNumber(); + } + } } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 04b6cc262..16719e8fc 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -67,6 +67,25 @@ public void testBlockQuotes() { assertRoundTrip("> # Foo\n> \n> bar\n> baz\n"); } + @Test + public void testBulletListItems() { + assertRoundTrip("* foo\n"); + assertRoundTrip("- foo\n"); + assertRoundTrip("+ foo\n"); + assertRoundTrip("* foo\n bar\n"); + assertRoundTrip("* ```\n code\n ```\n"); + assertRoundTrip("* foo\n\n* bar\n"); + + // Tight list +// assertRoundTrip("* foo\n* bar\n"); + } + + @Test + public void testOrderedListItems() { + assertRoundTrip("1. foo\n"); + assertRoundTrip("2. foo\n\n3. bar\n"); + } + // Inlines @Test diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 703b52bfc..c980c8682 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -51,7 +51,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 507; + int expectedPassed = 564; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From b82d7cee637fe322a6afc726c60562f91021c37d Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jan 2024 16:24:17 +1100 Subject: [PATCH 018/345] Percent encode URLs in spec test (like in other spec tests) --- .../renderer/markdown/SpecMarkdownRendererTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index c980c8682..d2ab3c110 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -27,7 +27,8 @@ public class SpecMarkdownRendererTest { public static final MarkdownRenderer MARKDOWN_RENDERER = MarkdownRenderer.builder().build(); - public static final HtmlRenderer HTML_RENDERER = HtmlRenderer.builder().build(); + // The spec says URL-escaping is optional, but the examples assume that it's enabled. + public static final HtmlRenderer HTML_RENDERER = HtmlRenderer.builder().percentEncodeUrls(true).build(); @Test public void testCoverage() { @@ -51,7 +52,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 564; + int expectedPassed = 574; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From d0e89f4ea7bc6a0b870abe8006911fb32ebcc2b0 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jan 2024 16:44:00 +1100 Subject: [PATCH 019/345] Fix code spans with spaces --- .../renderer/markdown/CoreMarkdownNodeRenderer.java | 5 ++++- .../commonmark/renderer/markdown/MarkdownRendererTest.java | 2 ++ .../renderer/markdown/SpecMarkdownRendererTest.java | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 77bd855ea..682545c65 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -98,7 +98,10 @@ public void visit(Code code) { writer.write('`'); } // If the literal starts or ends with a backtick, surround it with a single space. - boolean addSpace = literal.startsWith("`") || literal.endsWith("`"); + // If it starts and ends with a space (but is not only spaces), add an additional space (otherwise they would + // get removed on parsing). + boolean addSpace = literal.startsWith("`") || literal.endsWith("`") || + (literal.startsWith(" ") && literal.endsWith(" ") && Parsing.hasNonSpace(literal)); if (addSpace) { writer.write(' '); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 16719e8fc..a0cea3878 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -95,6 +95,8 @@ public void testCodeSpans() { assertRoundTrip("```foo `` ` bar```\n"); assertRoundTrip("`` `foo ``\n"); + assertRoundTrip("`` ` ``\n"); + assertRoundTrip("` `\n"); } @Test diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index d2ab3c110..e03c991fe 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -52,7 +52,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 574; + int expectedPassed = 575; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From a4eecb5e29d6d12e0562a1eca9e788c4618239ac Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jan 2024 16:48:32 +1100 Subject: [PATCH 020/345] Escape special characters in normal text --- .../renderer/markdown/CoreMarkdownNodeRenderer.java | 8 +++----- .../renderer/markdown/MarkdownRendererTest.java | 8 ++++++++ .../renderer/markdown/SpecMarkdownRendererTest.java | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 682545c65..22aa171df 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -15,6 +15,8 @@ */ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer { + private final CharMatcher textEscape = + AsciiMatcher.builder().c('[').c(']').c('<').c('>').c('`').build(); private final CharMatcher linkDestinationNeedsAngleBrackets = AsciiMatcher.builder().c(' ').c('(').c(')').c('<').c('>').c('\\').build(); private final CharMatcher linkDestinationEscapeInAngleBrackets = @@ -268,7 +270,7 @@ public void visit(StrongEmphasis strongEmphasis) { @Override public void visit(Text text) { - writeText(text.getLiteral()); + writer.writeEscaped(text.getLiteral(), textEscape); } @Override @@ -313,10 +315,6 @@ private static String repeat(String s, int count) { return sb.toString(); } - private void writeText(String text) { - writer.write(text); - } - private void writeLinkLike(String title, String destination, Node node, String opener) { writer.write(opener); visitChildren(node); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index a0cea3878..3c6925c03 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -88,6 +88,14 @@ public void testOrderedListItems() { // Inlines + @Test + public void testEscaping() { + // These are a bit tricky. We always escape some characters, even though they only need escaping if they would + // otherwise result in a different parse result (e.g. a link): + assertRoundTrip("\\[a\\](/uri)\n"); + assertRoundTrip("\\`abc\\`\n"); + } + @Test public void testCodeSpans() { assertRoundTrip("`foo`\n"); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index e03c991fe..37253fb6f 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -52,7 +52,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 575; + int expectedPassed = 590; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 9f7d9499a0b7e9ddc8fd880b3c5f1bf801ad59da Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jan 2024 17:03:45 +1100 Subject: [PATCH 021/345] Fix empty fenced code blocks --- .../renderer/markdown/CoreMarkdownNodeRenderer.java | 10 ++++++---- .../renderer/markdown/MarkdownRendererTest.java | 1 + .../renderer/markdown/SpecMarkdownRendererTest.java | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 22aa171df..34533c633 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -142,10 +142,12 @@ public void visit(FencedCodeBlock fencedCodeBlock) { writer.write(fencedCodeBlock.getInfo()); } writer.line(); - String[] lines = literal.split("\n"); - for (String line : lines) { - writer.write(line); - writer.line(); + if (!literal.isEmpty()) { + String[] lines = literal.split("\n"); + for (String line : lines) { + writer.write(line); + writer.line(); + } } writer.write(fence); if (indent > 0) { diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 3c6925c03..a1c848ccd 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -44,6 +44,7 @@ public void testFencedCodeBlocks() { assertRoundTrip("~~~~\ntest\n~~~~\n"); assertRoundTrip("```info\ntest\n```\n"); assertRoundTrip(" ```\n test\n ```\n"); + assertRoundTrip("```\n```\n"); } @Test diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 37253fb6f..14b9b5196 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -52,7 +52,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 590; + int expectedPassed = 594; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 29145aac3da21fb49286b46e7a7119a21bbed1d8 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jan 2024 17:13:28 +1100 Subject: [PATCH 022/345] Tight lists --- .../markdown/CoreMarkdownNodeRenderer.java | 6 ++++++ .../renderer/markdown/MarkdownWriter.java | 15 +++++++++++++-- .../renderer/markdown/MarkdownRendererTest.java | 5 ++++- .../markdown/SpecMarkdownRendererTest.java | 2 +- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 34533c633..7068227dd 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -86,9 +86,12 @@ public void visit(BlockQuote blockQuote) { @Override public void visit(BulletList bulletList) { + boolean oldTight = writer.getTight(); + writer.setTight(bulletList.isTight()); listHolder = new BulletListHolder(listHolder, bulletList); visitChildren(bulletList); listHolder = listHolder.parent; + writer.setTight(oldTight); } @Override @@ -243,9 +246,12 @@ public void visit(ListItem listItem) { @Override public void visit(OrderedList orderedList) { + boolean oldTight = writer.getTight(); + writer.setTight(orderedList.isTight()); listHolder = new OrderedListHolder(listHolder, orderedList); visitChildren(orderedList); listHolder = listHolder.parent; + writer.setTight(oldTight); } @Override diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index 71c820559..c2c826195 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -10,6 +10,7 @@ public class MarkdownWriter { private final Appendable buffer; private boolean finishBlock = false; + private boolean tight; private char lastChar; private final LinkedList prefixes = new LinkedList<>(); @@ -96,8 +97,10 @@ private void finishBlockIfNeeded() { finishBlock = false; append('\n'); writePrefixes(); - append('\n'); - writePrefixes(); + if (!tight) { + append('\n'); + writePrefixes(); + } } } @@ -108,4 +111,12 @@ private void writePrefixes() { } } } + + public boolean getTight() { + return tight; + } + + public void setTight(boolean tight) { + this.tight = tight; + } } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index a1c848ccd..b99209ef9 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -78,13 +78,16 @@ public void testBulletListItems() { assertRoundTrip("* foo\n\n* bar\n"); // Tight list -// assertRoundTrip("* foo\n* bar\n"); + assertRoundTrip("* foo\n* bar\n"); } @Test public void testOrderedListItems() { assertRoundTrip("1. foo\n"); assertRoundTrip("2. foo\n\n3. bar\n"); + + // Tight list + assertRoundTrip("1. foo\n2. bar\n"); } // Inlines diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 14b9b5196..e004366f6 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -52,7 +52,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 594; + int expectedPassed = 611; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 8171519f9b89a1257eabffde7e68edeb832fc72b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 17 Jan 2024 16:58:26 +1100 Subject: [PATCH 023/345] Preserve indent and content indent for lists Makes use of #303 which was split out. --- .../markdown/CoreMarkdownNodeRenderer.java | 15 +++++++++------ .../renderer/markdown/MarkdownRendererTest.java | 16 +++++++++++++++- .../markdown/SpecMarkdownRendererTest.java | 2 +- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 7068227dd..c583e6579 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -223,19 +223,22 @@ public void visit(Link link) { @Override public void visit(ListItem listItem) { + int contentIndent = listItem.getContentIndent(); boolean pushedPrefix = false; if (listHolder instanceof BulletListHolder) { BulletListHolder bulletListHolder = (BulletListHolder) listHolder; - String prefix = bulletListHolder.bulletMarker + " "; - writer.write(prefix); - writer.pushPrefix(repeat(" ", prefix.length())); + String marker = repeat(" ", listItem.getMarkerIndent()) + bulletListHolder.bulletMarker; + writer.write(marker); + writer.write(repeat(" ", contentIndent - marker.length())); + writer.pushPrefix(repeat(" ", contentIndent)); pushedPrefix = true; } else if (listHolder instanceof OrderedListHolder) { OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; - String prefix = String.valueOf(orderedListHolder.number) + orderedListHolder.delimiter + " "; + String marker = repeat(" ", listItem.getMarkerIndent()) + orderedListHolder.number + orderedListHolder.delimiter; orderedListHolder.number++; - writer.write(prefix); - writer.pushPrefix(repeat(" ", prefix.length())); + writer.write(marker); + writer.write(repeat(" ", contentIndent - marker.length())); + writer.pushPrefix(repeat(" ", contentIndent)); pushedPrefix = true; } visitChildren(listItem); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index b99209ef9..5b3148a2d 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -76,9 +76,20 @@ public void testBulletListItems() { assertRoundTrip("* foo\n bar\n"); assertRoundTrip("* ```\n code\n ```\n"); assertRoundTrip("* foo\n\n* bar\n"); + // Note that the " " in the second line is not necessary, but it's not wrong either. + // We could try to avoid it in a future change, but not sure if necessary. + assertRoundTrip("* foo\n \n bar\n"); // Tight list assertRoundTrip("* foo\n* bar\n"); + + // List item indent. This is a tricky one, but here the amount of space between the list marker and "one" + // determines whether "two" is part of the list item or an indented code block. + // In this case, it's an indented code block because it's not indented enough to be part of the list item. + // If the renderer would just use "- one", then "two" would change from being an indented code block to being + // a paragraph in the list item! So it is important for the renderer to preserve the content indent of the list + // item. + assertRoundTrip(" - one\n\n two\n"); } @Test @@ -88,6 +99,8 @@ public void testOrderedListItems() { // Tight list assertRoundTrip("1. foo\n2. bar\n"); + + assertRoundTrip(" 1. one\n\n two\n"); } // Inlines @@ -173,7 +186,8 @@ private Node parse(String source) { } private String render(String source) { - return MarkdownRenderer.builder().build().render(parse(source)); + Node parsed = parse(source); + return MarkdownRenderer.builder().build().render(parsed); } private void assertRoundTrip(String input) { diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index e004366f6..fbd3bc1a6 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -52,7 +52,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 611; + int expectedPassed = 613; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 41eeede32bb189d41b4c7f1fb7dfef12057d438e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 17 Jan 2024 17:01:49 +1100 Subject: [PATCH 024/345] Fix empty list items --- .../renderer/markdown/CoreMarkdownNodeRenderer.java | 7 ++++++- .../commonmark/renderer/markdown/MarkdownRendererTest.java | 3 +++ .../renderer/markdown/SpecMarkdownRendererTest.java | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index c583e6579..28461c870 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -241,7 +241,12 @@ public void visit(ListItem listItem) { writer.pushPrefix(repeat(" ", contentIndent)); pushedPrefix = true; } - visitChildren(listItem); + if (listItem.getFirstChild() == null) { + // Empty list item + writer.block(); + } else { + visitChildren(listItem); + } if (pushedPrefix) { writer.popPrefix(); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 5b3148a2d..06ad79c25 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -90,6 +90,9 @@ public void testBulletListItems() { // a paragraph in the list item! So it is important for the renderer to preserve the content indent of the list // item. assertRoundTrip(" - one\n\n two\n"); + + // Empty list + assertRoundTrip("- \n\nFoo\n"); } @Test diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index fbd3bc1a6..682fee9d8 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -52,7 +52,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 613; + int expectedPassed = 618; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 12ce6bc4244d9f0c06b2ff3bab753e6a42c0907e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 17 Jan 2024 22:43:30 +1100 Subject: [PATCH 025/345] Fix tight list with nested loose list --- .../markdown/CoreMarkdownNodeRenderer.java | 90 +++++++++---------- .../renderer/markdown/MarkdownWriter.java | 70 +++++++++------ .../markdown/MarkdownRendererTest.java | 4 + .../markdown/SpecMarkdownRendererTest.java | 2 +- 4 files changed, 94 insertions(+), 72 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 28461c870..5e8833331 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -92,6 +92,49 @@ public void visit(BulletList bulletList) { visitChildren(bulletList); listHolder = listHolder.parent; writer.setTight(oldTight); + writer.block(); + } + + @Override + public void visit(OrderedList orderedList) { + boolean oldTight = writer.getTight(); + writer.setTight(orderedList.isTight()); + listHolder = new OrderedListHolder(listHolder, orderedList); + visitChildren(orderedList); + listHolder = listHolder.parent; + writer.setTight(oldTight); + writer.block(); + } + + @Override + public void visit(ListItem listItem) { + int contentIndent = listItem.getContentIndent(); + boolean pushedPrefix = false; + if (listHolder instanceof BulletListHolder) { + BulletListHolder bulletListHolder = (BulletListHolder) listHolder; + String marker = repeat(" ", listItem.getMarkerIndent()) + bulletListHolder.bulletMarker; + writer.write(marker); + writer.write(repeat(" ", contentIndent - marker.length())); + writer.pushPrefix(repeat(" ", contentIndent)); + pushedPrefix = true; + } else if (listHolder instanceof OrderedListHolder) { + OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; + String marker = repeat(" ", listItem.getMarkerIndent()) + orderedListHolder.number + orderedListHolder.delimiter; + orderedListHolder.number++; + writer.write(marker); + writer.write(repeat(" ", contentIndent - marker.length())); + writer.pushPrefix(repeat(" ", contentIndent)); + pushedPrefix = true; + } + if (listItem.getFirstChild() == null) { + // Empty list item + writer.block(); + } else { + visitChildren(listItem); + } + if (pushedPrefix) { + writer.popPrefix(); + } } @Override @@ -221,55 +264,10 @@ public void visit(Link link) { writeLinkLike(link.getTitle(), link.getDestination(), link, "["); } - @Override - public void visit(ListItem listItem) { - int contentIndent = listItem.getContentIndent(); - boolean pushedPrefix = false; - if (listHolder instanceof BulletListHolder) { - BulletListHolder bulletListHolder = (BulletListHolder) listHolder; - String marker = repeat(" ", listItem.getMarkerIndent()) + bulletListHolder.bulletMarker; - writer.write(marker); - writer.write(repeat(" ", contentIndent - marker.length())); - writer.pushPrefix(repeat(" ", contentIndent)); - pushedPrefix = true; - } else if (listHolder instanceof OrderedListHolder) { - OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; - String marker = repeat(" ", listItem.getMarkerIndent()) + orderedListHolder.number + orderedListHolder.delimiter; - orderedListHolder.number++; - writer.write(marker); - writer.write(repeat(" ", contentIndent - marker.length())); - writer.pushPrefix(repeat(" ", contentIndent)); - pushedPrefix = true; - } - if (listItem.getFirstChild() == null) { - // Empty list item - writer.block(); - } else { - visitChildren(listItem); - } - if (pushedPrefix) { - writer.popPrefix(); - } - } - - @Override - public void visit(OrderedList orderedList) { - boolean oldTight = writer.getTight(); - writer.setTight(orderedList.isTight()); - listHolder = new OrderedListHolder(listHolder, orderedList); - visitChildren(orderedList); - listHolder = listHolder.parent; - writer.setTight(oldTight); - } - @Override public void visit(Paragraph paragraph) { visitChildren(paragraph); - if (paragraph.getParent() instanceof ListItem) { - writer.block(); - } else { - writer.block(); - } + writer.block(); } @Override diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index c2c826195..ba682d465 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -9,7 +9,7 @@ public class MarkdownWriter { private final Appendable buffer; - private boolean finishBlock = false; + private int blockSeparator = 0; private boolean tight; private char lastChar; private final LinkedList prefixes = new LinkedList<>(); @@ -22,22 +22,13 @@ public char getLastChar() { return lastChar; } - public void block() { - finishBlock = true; - } - - public void line() { - append('\n'); - writePrefixes(); - } - public void write(String s) { - finishBlockIfNeeded(); + flushBlockSeparator(); append(s); } public void write(char c) { - finishBlockIfNeeded(); + flushBlockSeparator(); append(c); } @@ -45,7 +36,7 @@ public void writeEscaped(String s, CharMatcher escape) { if (s.isEmpty()) { return; } - finishBlockIfNeeded(); + flushBlockSeparator(); try { for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); @@ -61,6 +52,21 @@ public void writeEscaped(String s, CharMatcher escape) { lastChar = s.charAt(s.length() - 1); } + public void line() { + append('\n'); + writePrefixes(); + } + + /** + * Enqueue a block separator to be written before the next text is written. Block separators are not written + * straight away because if there are no more blocks to write we don't want a separator (at the end of the document). + */ + public void block() { + // Remember whether this should be a tight or loose separator now because tight could get changed in between + // this and the next flush. + blockSeparator = tight ? 1 : 2; + } + public void pushPrefix(String prefix) { prefixes.addLast(prefix); } @@ -92,18 +98,6 @@ private void append(char c) { lastChar = c; } - private void finishBlockIfNeeded() { - if (finishBlock) { - finishBlock = false; - append('\n'); - writePrefixes(); - if (!tight) { - append('\n'); - writePrefixes(); - } - } - } - private void writePrefixes() { if (!prefixes.isEmpty()) { for (String prefix : prefixes) { @@ -112,10 +106,36 @@ private void writePrefixes() { } } + /** + * If a block separator has been enqueued with {@link #block()} but not yet written, write it now. + */ + private void flushBlockSeparator() { + if (blockSeparator != 0) { + append('\n'); + writePrefixes(); + if (blockSeparator > 1) { + append('\n'); + writePrefixes(); + } + blockSeparator = 0; + } + } + + /** + * @return whether blocks are currently set to tight or loose, see {@link #setTight(boolean)} + */ public boolean getTight() { return tight; } + /** + * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight + * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines + * within the list. + *

+ * Note that changing this does not affect block separators that have already been enqueued (with {@link #block()}, + * only future ones. + */ public void setTight(boolean tight) { this.tight = tight; } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 06ad79c25..f6ce1b4e0 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -82,6 +82,8 @@ public void testBulletListItems() { // Tight list assertRoundTrip("* foo\n* bar\n"); + // Tight list where the second item contains a loose list + assertRoundTrip("- Foo\n - Bar\n \n - Baz\n"); // List item indent. This is a tricky one, but here the amount of space between the list marker and "one" // determines whether "two" is part of the list item or an indented code block. @@ -102,6 +104,8 @@ public void testOrderedListItems() { // Tight list assertRoundTrip("1. foo\n2. bar\n"); + // Tight list where the second item contains a loose list + assertRoundTrip("1. Foo\n 1. Bar\n \n 2. Baz\n"); assertRoundTrip(" 1. one\n\n two\n"); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 682fee9d8..e21ec8cc7 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -52,7 +52,7 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); - int expectedPassed = 618; + int expectedPassed = 621; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 6417d012e5476b8abeaf89b5b19f35c2d9477a32 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 17 Jan 2024 22:54:06 +1100 Subject: [PATCH 026/345] Print failing cases --- .../renderer/markdown/SpecMarkdownRendererTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index e21ec8cc7..d50349d34 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -51,6 +51,16 @@ public void testCoverage() { System.out.println("Failed examples by section (total " + fails.size() + "):"); printCountsBySection(fails); + System.out.println(); + + System.out.println("Failed examples:"); + for (Example fail : fails) { + System.out.println("Failed: " + fail); + System.out.println("````````````````````````````````"); + System.out.print(fail.getSource()); + System.out.println("````````````````````````````````"); + System.out.println(); + } int expectedPassed = 621; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); From 26ac7e1c42821cd166e7cf91d657936dcc63accb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 22 Jan 2024 22:09:49 +1100 Subject: [PATCH 027/345] Don't discard trailing empty lines in code blocks --- .../markdown/CoreMarkdownNodeRenderer.java | 25 +++++++++++++++---- .../markdown/SpecMarkdownRendererTest.java | 2 +- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 5e8833331..a135d73ee 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -8,6 +8,7 @@ import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -189,7 +190,7 @@ public void visit(FencedCodeBlock fencedCodeBlock) { } writer.line(); if (!literal.isEmpty()) { - String[] lines = literal.split("\n"); + List lines = getLines(literal); for (String line : lines) { writer.write(line); writer.line(); @@ -243,15 +244,15 @@ public void visit(Image image) { @Override public void visit(IndentedCodeBlock indentedCodeBlock) { String literal = indentedCodeBlock.getLiteral(); - String[] lines = literal.split("\n"); // We need to respect line prefixes which is why we need to write it line by line (e.g. an indented code block // within a block quote) writer.write(" "); writer.pushPrefix(" "); - for (int i = 0; i < lines.length; i++) { - String line = lines[i]; + List lines = getLines(literal); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); writer.write(line); - if (i != lines.length - 1) { + if (i != lines.size() - 1) { writer.line(); } } @@ -329,6 +330,20 @@ private static String repeat(String s, int count) { return sb.toString(); } + private static List getLines(String literal) { + // Without -1, split would discard all trailing empty strings, which is not what we want, e.g. it would + // return the same result for "abc", "abc\n" and "abc\n\n". + // With -1, it returns ["abc"], ["abc", ""] and ["abc", "", ""]. + String[] parts = literal.split("\n", -1); + if (parts[parts.length - 1].isEmpty()) { + // But we don't want the last empty string, as "\n" is used as a line terminator (not a separator), + // so return without the last element. + return Arrays.asList(parts).subList(0, parts.length - 1); + } else { + return Arrays.asList(parts); + } + } + private void writeLinkLike(String title, String destination, Node node, String opener) { writer.write(opener); visitChildren(node); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index d50349d34..4e9396e77 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -62,7 +62,7 @@ public void testCoverage() { System.out.println(); } - int expectedPassed = 621; + int expectedPassed = 622; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 724619b0efc5937b4fba974769cea7fa6049dffc Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 22 Jan 2024 22:46:33 +1100 Subject: [PATCH 028/345] Use Setext heading if necessary --- .../markdown/CoreMarkdownNodeRenderer.java | 47 +++++++++++++++++++ .../renderer/markdown/MarkdownRenderer.java | 2 +- .../markdown/MarkdownRendererTest.java | 3 ++ .../markdown/SpecMarkdownRendererTest.java | 2 +- 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index a135d73ee..dbaa9a47c 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -13,6 +13,10 @@ /** * The node renderer that renders all the core nodes (comes last in the order of node renderers). + *

+ * Note that while sometimes it would be easier to record what kind of syntax was used on parsing (e.g. ATX vs Setext + * heading), this renderer is intended to also work for documents that were created by directly creating + * {@link Node Nodes} instead. So in order to support that, it sometimes needs to do a bit more work. */ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer { @@ -211,11 +215,34 @@ public void visit(HardLineBreak hardLineBreak) { @Override public void visit(Heading heading) { + if (heading.getLevel() <= 2) { + LineBreakVisitor lineBreakVisitor = new LineBreakVisitor(); + heading.accept(lineBreakVisitor); + boolean isMultipleLines = lineBreakVisitor.hasLineBreak(); + + if (isMultipleLines) { + // Setext headings: Can have multiple lines, but only level 1 or 2 + visitChildren(heading); + writer.line(); + if (heading.getLevel() == 1) { + // Note that it would be nice to match the length of the contents instead of just using 3, but that's + // not easy. + writer.write("==="); + } else { + writer.write("---"); + } + writer.block(); + return; + } + } + + // ATX headings: Can't have multiple lines, but up to level 6. for (int i = 0; i < heading.getLevel(); i++) { writer.write('#'); } writer.write(' '); visitChildren(heading); + writer.block(); } @@ -392,4 +419,24 @@ protected OrderedListHolder(ListHolder parent, OrderedList orderedList) { number = orderedList.getStartNumber(); } } + + private static class LineBreakVisitor extends AbstractVisitor { + private boolean lineBreak = false; + + public boolean hasLineBreak() { + return lineBreak; + } + + @Override + public void visit(SoftLineBreak softLineBreak) { + super.visit(softLineBreak); + lineBreak = true; + } + + @Override + public void visit(HardLineBreak hardLineBreak) { + super.visit(hardLineBreak); + lineBreak = true; + } + } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java index 4dc8dbff9..4dee17ed6 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java @@ -14,7 +14,7 @@ *

* Note that it does not currently attempt to preserve the exact syntax of the original input Markdown (if any): *

    - *
  • Headings are always output as ATX headings for simplicity
  • + *
  • Headings are output as ATX headings if possible (multi-line headings need Setext headings)
  • *
  • Escaping might be over-eager, e.g. a plain {@code *} might be escaped * even though it doesn't need to be in that particular context
  • *
diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index f6ce1b4e0..05a253fde 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -28,6 +28,9 @@ public void testHeadings() { assertRoundTrip("##### foo\n"); assertRoundTrip("###### foo\n"); + assertRoundTrip("Foo\nbar\n===\n"); + assertRoundTrip("[foo\nbar](/url)\n===\n"); + assertRoundTrip("# foo\n\nbar\n"); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 4e9396e77..1dd064414 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -62,7 +62,7 @@ public void testCoverage() { System.out.println(); } - int expectedPassed = 622; + int expectedPassed = 625; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 02670c91eebb750dd1e2b65fea40382e9fe1be57 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 22 Jan 2024 23:02:59 +1100 Subject: [PATCH 029/345] Test tabs properly --- .../commonmark/renderer/markdown/MarkdownRendererTest.java | 5 +++++ .../renderer/markdown/SpecMarkdownRendererTest.java | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 05a253fde..ae3396680 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -115,6 +115,11 @@ public void testOrderedListItems() { // Inlines + @Test + public void testTabs() { + assertRoundTrip("a\tb\n"); + } + @Test public void testEscaping() { // These are a bit tricky. We always escape some characters, even though they only need escaping if they would diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 1dd064414..de657e2cd 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -62,7 +62,7 @@ public void testCoverage() { System.out.println(); } - int expectedPassed = 625; + int expectedPassed = 629; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } @@ -89,6 +89,7 @@ private String renderMarkdown(String source) { } private String renderHtml(String source) { - return HTML_RENDERER.render(parse(source)); + // The spec uses "rightwards arrow" to show tabs + return HTML_RENDERER.render(parse(source)).replace("\t", "\u2192"); } } From 4918d64d592280490b54c77de066ec15c8582b1a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 22 Jan 2024 23:10:31 +1100 Subject: [PATCH 030/345] Write HTML block in lines to make use of prefix --- .../renderer/markdown/CoreMarkdownNodeRenderer.java | 9 ++++++++- .../renderer/markdown/MarkdownRendererTest.java | 1 + .../renderer/markdown/SpecMarkdownRendererTest.java | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index dbaa9a47c..813efb164 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -259,7 +259,14 @@ public void visit(HtmlInline htmlInline) { @Override public void visit(HtmlBlock htmlBlock) { - writer.write(htmlBlock.getLiteral()); + List lines = getLines(htmlBlock.getLiteral()); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + writer.write(line); + if (i != lines.size() - 1) { + writer.line(); + } + } writer.block(); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index ae3396680..76a77dbb5 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -53,6 +53,7 @@ public void testFencedCodeBlocks() { @Test public void testHtmlBlocks() { assertRoundTrip("
test
\n"); + assertRoundTrip(">
\n> test\n>
\n"); } @Test diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index de657e2cd..2a0c46c00 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -62,7 +62,7 @@ public void testCoverage() { System.out.println(); } - int expectedPassed = 629; + int expectedPassed = 630; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From db5f055f0c456bd852601bd0de61336c75bd9bd7 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 23 Jan 2024 22:43:20 +1100 Subject: [PATCH 031/345] Escape special characters at beginning of line --- .../internal/util/AsciiMatcher.java | 7 +++ .../markdown/CoreMarkdownNodeRenderer.java | 58 ++++++++++++++++++- .../renderer/markdown/MarkdownWriter.java | 6 +- .../markdown/MarkdownRendererTest.java | 16 +++++ .../markdown/SpecMarkdownRendererTest.java | 2 +- 5 files changed, 84 insertions(+), 5 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java b/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java index 82d83ca46..35769f82d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java @@ -29,6 +29,13 @@ private Builder(BitSet set) { this.set = set; } + public Builder anyOf(String s) { + for (int i = 0; i < s.length(); i++) { + c(s.charAt(i)); + } + return this; + } + public Builder c(char c) { if (c > 127) { throw new IllegalArgumentException("Can only match ASCII characters"); diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 813efb164..fe5725aee 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -10,6 +10,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * The node renderer that renders all the core nodes (comes last in the order of node renderers). @@ -20,8 +22,8 @@ */ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer { - private final CharMatcher textEscape = - AsciiMatcher.builder().c('[').c(']').c('<').c('>').c('`').build(); + private final AsciiMatcher textEscape = + AsciiMatcher.builder().anyOf("[]<>`*&").build(); private final CharMatcher linkDestinationNeedsAngleBrackets = AsciiMatcher.builder().c(' ').c('(').c(')').c('<').c('>').c('\\').build(); private final CharMatcher linkDestinationEscapeInAngleBrackets = @@ -29,6 +31,8 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen private final CharMatcher linkTitleEscapeInQuotes = AsciiMatcher.builder().c('"').build(); + private final Pattern orderedListMarkerPattern = Pattern.compile("^([0-9]{1,9})([.)])"); + protected final MarkdownNodeRendererContext context; private final MarkdownWriter writer; /** @@ -319,7 +323,55 @@ public void visit(StrongEmphasis strongEmphasis) { @Override public void visit(Text text) { - writer.writeEscaped(text.getLiteral(), textEscape); + String literal = text.getLiteral(); + if (writer.isAtLineStart() && !literal.isEmpty()) { + char c = literal.charAt(0); + switch (c) { + case '-': { + // Would be ambiguous with a bullet list marker, escape + writer.write("\\-"); + literal = literal.substring(1); + break; + } + case '#': { + // Would be ambiguous with an ATX heading, escape + writer.write("\\#"); + literal = literal.substring(1); + break; + } + case '=': { + // Would be ambiguous with a Setext heading, escape + writer.write("\\="); + literal = literal.substring(1); + break; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + // Check for ordered list marker + Matcher m = orderedListMarkerPattern.matcher(literal); + if (m.find()) { + writer.write(m.group(1)); + writer.write("\\" + m.group(2)); + literal = literal.substring(m.end()); + } + } + } + } + + if (literal.endsWith("!") && text.getNext() instanceof Link) { + writer.writeEscaped(literal.substring(0, literal.length() - 1), textEscape); + writer.write("\\!"); + } else { + writer.writeEscaped(literal, textEscape); + } } @Override diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index ba682d465..bc10f020e 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -11,7 +11,7 @@ public class MarkdownWriter { private int blockSeparator = 0; private boolean tight; - private char lastChar; + private char lastChar = '\n'; private final LinkedList prefixes = new LinkedList<>(); public MarkdownWriter(Appendable out) { @@ -22,6 +22,10 @@ public char getLastChar() { return lastChar; } + public boolean isAtLineStart() { + return lastChar == '\n' || blockSeparator > 0; + } + public void write(String s) { flushBlockSeparator(); append(s); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 76a77dbb5..59039799a 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -127,6 +127,22 @@ public void testEscaping() { // otherwise result in a different parse result (e.g. a link): assertRoundTrip("\\[a\\](/uri)\n"); assertRoundTrip("\\`abc\\`\n"); + + // Some characters only need to be escaped at the beginning of the line + assertRoundTrip("\\- Test\n"); + assertRoundTrip("\\-\n"); + assertRoundTrip("Test -\n"); + assertRoundTrip("Abc\n\n\\- Test\n"); + assertRoundTrip("\\# Test\n"); + assertRoundTrip("\\## Test\n"); + assertRoundTrip("\\#\n"); + assertRoundTrip("Foo\n\\===\n"); + + // This is a bit more tricky as we need to check for a list start + assertRoundTrip("1\\. Foo\n"); + assertRoundTrip("999\\. Foo\n"); + assertRoundTrip("1\\.\n"); + assertRoundTrip("1\\) Foo\n"); } @Test diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 2a0c46c00..632f4acfc 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -62,7 +62,7 @@ public void testCoverage() { System.out.println(); } - int expectedPassed = 630; + int expectedPassed = 646; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 64d163d2e6b2719affe4fd40d31eefa44bf52711 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 23 Jan 2024 22:45:29 +1100 Subject: [PATCH 032/345] Escape # in headings too --- .../java/org/commonmark/internal/util/AsciiMatcher.java | 4 ++++ .../renderer/markdown/CoreMarkdownNodeRenderer.java | 8 ++++++-- .../renderer/markdown/SpecMarkdownRendererTest.java | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java b/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java index 35769f82d..d31020fa3 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java @@ -22,6 +22,10 @@ public static Builder builder() { return new Builder(new BitSet()); } + public static Builder builder(AsciiMatcher matcher) { + return new Builder((BitSet) matcher.set.clone()); + } + public static class Builder { private final BitSet set; diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index fe5725aee..8cb67e0fd 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -24,6 +24,8 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen private final AsciiMatcher textEscape = AsciiMatcher.builder().anyOf("[]<>`*&").build(); + private final CharMatcher textEscapeInHeading = + AsciiMatcher.builder(textEscape).anyOf("#").build(); private final CharMatcher linkDestinationNeedsAngleBrackets = AsciiMatcher.builder().c(' ').c('(').c(')').c('<').c('>').c('\\').build(); private final CharMatcher linkDestinationEscapeInAngleBrackets = @@ -366,11 +368,13 @@ public void visit(Text text) { } } + CharMatcher escape = text.getParent() instanceof Heading ? textEscapeInHeading : textEscape; + if (literal.endsWith("!") && text.getNext() instanceof Link) { - writer.writeEscaped(literal.substring(0, literal.length() - 1), textEscape); + writer.writeEscaped(literal.substring(0, literal.length() - 1), escape); writer.write("\\!"); } else { - writer.writeEscaped(literal, textEscape); + writer.writeEscaped(literal, escape); } } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 632f4acfc..d6bdf9cfb 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -62,7 +62,7 @@ public void testCoverage() { System.out.println(); } - int expectedPassed = 646; + int expectedPassed = 647; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From f5b04efdae2c48e46f8f108df71308a7521ac223 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 23 Jan 2024 23:35:48 +1100 Subject: [PATCH 033/345] Disregard prefixes for line start logic --- .../markdown/CoreMarkdownNodeRenderer.java | 14 +++++++------- .../renderer/markdown/MarkdownWriter.java | 19 +++++++++++++++++-- .../markdown/MarkdownRendererTest.java | 5 +++++ .../markdown/SpecMarkdownRendererTest.java | 2 +- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 8cb67e0fd..11e7ae5f0 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -88,7 +88,7 @@ public void visit(Document document) { @Override public void visit(BlockQuote blockQuote) { - writer.write("> "); + writer.writePrefix("> "); writer.pushPrefix("> "); visitChildren(blockQuote); writer.popPrefix(); @@ -124,16 +124,16 @@ public void visit(ListItem listItem) { if (listHolder instanceof BulletListHolder) { BulletListHolder bulletListHolder = (BulletListHolder) listHolder; String marker = repeat(" ", listItem.getMarkerIndent()) + bulletListHolder.bulletMarker; - writer.write(marker); - writer.write(repeat(" ", contentIndent - marker.length())); + writer.writePrefix(marker); + writer.writePrefix(repeat(" ", contentIndent - marker.length())); writer.pushPrefix(repeat(" ", contentIndent)); pushedPrefix = true; } else if (listHolder instanceof OrderedListHolder) { OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; String marker = repeat(" ", listItem.getMarkerIndent()) + orderedListHolder.number + orderedListHolder.delimiter; orderedListHolder.number++; - writer.write(marker); - writer.write(repeat(" ", contentIndent - marker.length())); + writer.writePrefix(marker); + writer.writePrefix(repeat(" ", contentIndent - marker.length())); writer.pushPrefix(repeat(" ", contentIndent)); pushedPrefix = true; } @@ -190,7 +190,7 @@ public void visit(FencedCodeBlock fencedCodeBlock) { if (indent > 0) { String indentPrefix = repeat(" ", indent); - writer.write(indentPrefix); + writer.writePrefix(indentPrefix); writer.pushPrefix(indentPrefix); } @@ -286,7 +286,7 @@ public void visit(IndentedCodeBlock indentedCodeBlock) { String literal = indentedCodeBlock.getLiteral(); // We need to respect line prefixes which is why we need to write it line by line (e.g. an indented code block // within a block quote) - writer.write(" "); + writer.writePrefix(" "); writer.pushPrefix(" "); List lines = getLines(literal); for (int i = 0; i < lines.size(); i++) { diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index bc10f020e..d95c0bd87 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -11,7 +11,8 @@ public class MarkdownWriter { private int blockSeparator = 0; private boolean tight; - private char lastChar = '\n'; + private char lastChar; + private boolean atLineStart = true; private final LinkedList prefixes = new LinkedList<>(); public MarkdownWriter(Appendable out) { @@ -22,8 +23,11 @@ public char getLastChar() { return lastChar; } + /** + * @return whether we're at the line start (not counting any prefixes), i.e. after a {@link #line} or {@link #block}. + */ public boolean isAtLineStart() { - return lastChar == '\n' || blockSeparator > 0; + return atLineStart; } public void write(String s) { @@ -54,11 +58,13 @@ public void writeEscaped(String s, CharMatcher escape) { } lastChar = s.charAt(s.length() - 1); + atLineStart = false; } public void line() { append('\n'); writePrefixes(); + atLineStart = true; } /** @@ -69,12 +75,19 @@ public void block() { // Remember whether this should be a tight or loose separator now because tight could get changed in between // this and the next flush. blockSeparator = tight ? 1 : 2; + atLineStart = true; } public void pushPrefix(String prefix) { prefixes.addLast(prefix); } + public void writePrefix(String prefix) { + boolean tmp = atLineStart; + write(prefix); + atLineStart = tmp; + } + public void popPrefix() { prefixes.removeLast(); } @@ -90,6 +103,7 @@ private void append(String s) { if (length != 0) { lastChar = s.charAt(length - 1); } + atLineStart = false; } private void append(char c) { @@ -100,6 +114,7 @@ private void append(char c) { } lastChar = c; + atLineStart = false; } private void writePrefixes() { diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 59039799a..6f9e1e6f4 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -137,6 +137,11 @@ public void testEscaping() { assertRoundTrip("\\## Test\n"); assertRoundTrip("\\#\n"); assertRoundTrip("Foo\n\\===\n"); + // The beginning of the line within the block, so disregarding prefixes + assertRoundTrip("> \\- Test\n"); + assertRoundTrip("- \\- Test\n"); + // That's not the beginning of the line + assertRoundTrip("`a`- foo\n"); // This is a bit more tricky as we need to check for a list start assertRoundTrip("1\\. Foo\n"); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index d6bdf9cfb..39269368e 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -62,7 +62,7 @@ public void testCoverage() { System.out.println(); } - int expectedPassed = 647; + int expectedPassed = 650; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); } From 59d5c030969c57ce0f23b8eb619283cb2d7bf5ea Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 24 Jan 2024 14:53:33 +1100 Subject: [PATCH 034/345] Escape whitespace too (weird) --- .../renderer/markdown/CoreMarkdownNodeRenderer.java | 11 +++++++++++ .../commonmark/renderer/markdown/MarkdownWriter.java | 11 ++++++++--- .../renderer/markdown/MarkdownRendererTest.java | 5 +++++ .../renderer/markdown/SpecMarkdownRendererTest.java | 4 +++- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 11e7ae5f0..207851113 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -364,6 +364,17 @@ public void visit(Text text) { writer.write("\\" + m.group(2)); literal = literal.substring(m.end()); } + break; + } + case '\t': { + writer.write(" "); + literal = literal.substring(1); + break; + } + case ' ': { + writer.write(" "); + literal = literal.substring(1); + break; } } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index d95c0bd87..ef2945359 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -48,10 +48,15 @@ public void writeEscaped(String s, CharMatcher escape) { try { for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); - if (ch == '\\' || escape.matches(ch)) { - buffer.append('\\'); + if (ch == '\n') { + // Can't escape this with \, use numeric character reference + buffer.append(" "); + } else { + if (ch == '\\' || escape.matches(ch)) { + buffer.append('\\'); + } + buffer.append(ch); } - buffer.append(ch); } } catch (IOException e) { throw new RuntimeException(e); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 6f9e1e6f4..bc6d9b696 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -148,6 +148,11 @@ public void testEscaping() { assertRoundTrip("999\\. Foo\n"); assertRoundTrip("1\\.\n"); assertRoundTrip("1\\) Foo\n"); + + // Escaped whitespace, wow + assertRoundTrip(" foo\n"); + assertRoundTrip(" foo\n"); + assertRoundTrip("foo bar\n"); } @Test diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 39269368e..5df2e5c80 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** @@ -62,8 +63,9 @@ public void testCoverage() { System.out.println(); } - int expectedPassed = 650; + int expectedPassed = 652; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); + assertEquals(0, fails.size()); } private static void printCountsBySection(List examples) { From 37e507ab08912d3fd8e9753f86ed1dd7e84f9b69 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 24 Jan 2024 21:57:17 +1100 Subject: [PATCH 035/345] Reorder methods in spec order --- .../markdown/CoreMarkdownNodeRenderer.java | 238 +++++++++--------- 1 file changed, 119 insertions(+), 119 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 207851113..30c6332b9 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -86,6 +86,114 @@ public void visit(Document document) { writer.line(); } + @Override + public void visit(ThematicBreak thematicBreak) { + writer.write("***"); + writer.block(); + } + + @Override + public void visit(Heading heading) { + if (heading.getLevel() <= 2) { + LineBreakVisitor lineBreakVisitor = new LineBreakVisitor(); + heading.accept(lineBreakVisitor); + boolean isMultipleLines = lineBreakVisitor.hasLineBreak(); + + if (isMultipleLines) { + // Setext headings: Can have multiple lines, but only level 1 or 2 + visitChildren(heading); + writer.line(); + if (heading.getLevel() == 1) { + // Note that it would be nice to match the length of the contents instead of just using 3, but that's + // not easy. + writer.write("==="); + } else { + writer.write("---"); + } + writer.block(); + return; + } + } + + // ATX headings: Can't have multiple lines, but up to level 6. + for (int i = 0; i < heading.getLevel(); i++) { + writer.write('#'); + } + writer.write(' '); + visitChildren(heading); + + writer.block(); + } + + @Override + public void visit(IndentedCodeBlock indentedCodeBlock) { + String literal = indentedCodeBlock.getLiteral(); + // We need to respect line prefixes which is why we need to write it line by line (e.g. an indented code block + // within a block quote) + writer.writePrefix(" "); + writer.pushPrefix(" "); + List lines = getLines(literal); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + writer.write(line); + if (i != lines.size() - 1) { + writer.line(); + } + } + writer.popPrefix(); + writer.block(); + } + + @Override + public void visit(FencedCodeBlock fencedCodeBlock) { + String literal = fencedCodeBlock.getLiteral(); + String fence = repeat(String.valueOf(fencedCodeBlock.getFenceChar()), fencedCodeBlock.getFenceLength()); + int indent = fencedCodeBlock.getFenceIndent(); + + if (indent > 0) { + String indentPrefix = repeat(" ", indent); + writer.writePrefix(indentPrefix); + writer.pushPrefix(indentPrefix); + } + + writer.write(fence); + if (fencedCodeBlock.getInfo() != null) { + writer.write(fencedCodeBlock.getInfo()); + } + writer.line(); + if (!literal.isEmpty()) { + List lines = getLines(literal); + for (String line : lines) { + writer.write(line); + writer.line(); + } + } + writer.write(fence); + if (indent > 0) { + writer.popPrefix(); + } + writer.block(); + } + + @Override + public void visit(HtmlBlock htmlBlock) { + List lines = getLines(htmlBlock.getLiteral()); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + writer.write(line); + if (i != lines.size() - 1) { + writer.line(); + } + } + writer.block(); + } + + @Override + public void visit(Paragraph paragraph) { + visitChildren(paragraph); + writer.block(); + } + @Override public void visit(BlockQuote blockQuote) { writer.writePrefix("> "); @@ -183,97 +291,15 @@ public void visit(Emphasis emphasis) { } @Override - public void visit(FencedCodeBlock fencedCodeBlock) { - String literal = fencedCodeBlock.getLiteral(); - String fence = repeat(String.valueOf(fencedCodeBlock.getFenceChar()), fencedCodeBlock.getFenceLength()); - int indent = fencedCodeBlock.getFenceIndent(); - - if (indent > 0) { - String indentPrefix = repeat(" ", indent); - writer.writePrefix(indentPrefix); - writer.pushPrefix(indentPrefix); - } - - writer.write(fence); - if (fencedCodeBlock.getInfo() != null) { - writer.write(fencedCodeBlock.getInfo()); - } - writer.line(); - if (!literal.isEmpty()) { - List lines = getLines(literal); - for (String line : lines) { - writer.write(line); - writer.line(); - } - } - writer.write(fence); - if (indent > 0) { - writer.popPrefix(); - } - writer.block(); - } - - @Override - public void visit(HardLineBreak hardLineBreak) { - writer.write(" "); - writer.line(); - } - - @Override - public void visit(Heading heading) { - if (heading.getLevel() <= 2) { - LineBreakVisitor lineBreakVisitor = new LineBreakVisitor(); - heading.accept(lineBreakVisitor); - boolean isMultipleLines = lineBreakVisitor.hasLineBreak(); - - if (isMultipleLines) { - // Setext headings: Can have multiple lines, but only level 1 or 2 - visitChildren(heading); - writer.line(); - if (heading.getLevel() == 1) { - // Note that it would be nice to match the length of the contents instead of just using 3, but that's - // not easy. - writer.write("==="); - } else { - writer.write("---"); - } - writer.block(); - return; - } - } - - // ATX headings: Can't have multiple lines, but up to level 6. - for (int i = 0; i < heading.getLevel(); i++) { - writer.write('#'); - } - writer.write(' '); - visitChildren(heading); - - writer.block(); - } - - @Override - public void visit(ThematicBreak thematicBreak) { - writer.write("***"); - writer.block(); - } - - @Override - public void visit(HtmlInline htmlInline) { - writer.write(htmlInline.getLiteral()); + public void visit(StrongEmphasis strongEmphasis) { + writer.write("**"); + super.visit(strongEmphasis); + writer.write("**"); } @Override - public void visit(HtmlBlock htmlBlock) { - List lines = getLines(htmlBlock.getLiteral()); - for (int i = 0; i < lines.size(); i++) { - String line = lines.get(i); - writer.write(line); - if (i != lines.size() - 1) { - writer.line(); - } - } - writer.block(); + public void visit(Link link) { + writeLinkLike(link.getTitle(), link.getDestination(), link, "["); } @Override @@ -282,33 +308,14 @@ public void visit(Image image) { } @Override - public void visit(IndentedCodeBlock indentedCodeBlock) { - String literal = indentedCodeBlock.getLiteral(); - // We need to respect line prefixes which is why we need to write it line by line (e.g. an indented code block - // within a block quote) - writer.writePrefix(" "); - writer.pushPrefix(" "); - List lines = getLines(literal); - for (int i = 0; i < lines.size(); i++) { - String line = lines.get(i); - writer.write(line); - if (i != lines.size() - 1) { - writer.line(); - } - } - writer.popPrefix(); - writer.block(); - } - - @Override - public void visit(Link link) { - writeLinkLike(link.getTitle(), link.getDestination(), link, "["); + public void visit(HtmlInline htmlInline) { + writer.write(htmlInline.getLiteral()); } @Override - public void visit(Paragraph paragraph) { - visitChildren(paragraph); - writer.block(); + public void visit(HardLineBreak hardLineBreak) { + writer.write(" "); + writer.line(); } @Override @@ -316,13 +323,6 @@ public void visit(SoftLineBreak softLineBreak) { writer.line(); } - @Override - public void visit(StrongEmphasis strongEmphasis) { - writer.write("**"); - super.visit(strongEmphasis); - writer.write("**"); - } - @Override public void visit(Text text) { String literal = text.getLiteral(); From edfc3d025503b38799b63db261196f6fe4668add Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 29 Jan 2024 17:30:36 +1100 Subject: [PATCH 036/345] Some more comments --- .../markdown/CoreMarkdownNodeRenderer.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 30c6332b9..199add94d 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -51,26 +51,26 @@ public CoreMarkdownNodeRenderer(MarkdownNodeRendererContext context) { @Override public Set> getNodeTypes() { return new HashSet<>(Arrays.asList( - Document.class, - Heading.class, - Paragraph.class, BlockQuote.class, BulletList.class, + Code.class, + Document.class, + Emphasis.class, FencedCodeBlock.class, + HardLineBreak.class, + Heading.class, HtmlBlock.class, - ThematicBreak.class, + HtmlInline.class, + Image.class, IndentedCodeBlock.class, Link.class, ListItem.class, OrderedList.class, - Image.class, - Emphasis.class, + Paragraph.class, + SoftLineBreak.class, StrongEmphasis.class, Text.class, - Code.class, - HtmlInline.class, - SoftLineBreak.class, - HardLineBreak.class + ThematicBreak.class )); } @@ -325,6 +325,14 @@ public void visit(SoftLineBreak softLineBreak) { @Override public void visit(Text text) { + // Text is tricky. In Markdown special characters (`-`, `#` etc.) can be escaped (`\-`, `\#` etc.) so that + // they're parsed as plain text. Currently, whether a character was escaped or not is not recorded in the Node, + // so here we don't know. If we just wrote out those characters unescaped, the resulting Markdown would change + // meaning (turn into a list item, heading, etc.). + // You might say "Why not store that in the Node when parsing", but that wouldn't work for the use case where + // nodes are constructed directly instead of via parsing. This renderer needs to work for that too. + // So currently, when in doubt, we escape. For special characters only occurring at the beginning of a line, + // we only escape them then (we wouldn't want to escape every `.` for example). String literal = text.getLiteral(); if (writer.isAtLineStart() && !literal.isEmpty()) { char c = literal.charAt(0); @@ -382,6 +390,7 @@ public void visit(Text text) { CharMatcher escape = text.getParent() instanceof Heading ? textEscapeInHeading : textEscape; if (literal.endsWith("!") && text.getNext() instanceof Link) { + // If we wrote the `!` unescaped, it would turn the link into an image instead. writer.writeEscaped(literal.substring(0, literal.length() - 1), escape); writer.write("\\!"); } else { @@ -494,6 +503,9 @@ protected OrderedListHolder(ListHolder parent, OrderedList orderedList) { } } + /** + * Visits nodes to check if there are any soft or hard line breaks. + */ private static class LineBreakVisitor extends AbstractVisitor { private boolean lineBreak = false; From 08a60f11ce3c69042e2ce9f532a77c0cb5a0733f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 29 Jan 2024 17:39:58 +1100 Subject: [PATCH 037/345] Reorder methods in writer, add comments --- .../markdown/CoreMarkdownNodeRenderer.java | 88 ++++++++--------- .../renderer/markdown/MarkdownWriter.java | 99 +++++++++++++------ 2 files changed, 111 insertions(+), 76 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 199add94d..f5a9377f6 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -88,7 +88,7 @@ public void visit(Document document) { @Override public void visit(ThematicBreak thematicBreak) { - writer.write("***"); + writer.raw("***"); writer.block(); } @@ -106,9 +106,9 @@ public void visit(Heading heading) { if (heading.getLevel() == 1) { // Note that it would be nice to match the length of the contents instead of just using 3, but that's // not easy. - writer.write("==="); + writer.raw("==="); } else { - writer.write("---"); + writer.raw("---"); } writer.block(); return; @@ -117,9 +117,9 @@ public void visit(Heading heading) { // ATX headings: Can't have multiple lines, but up to level 6. for (int i = 0; i < heading.getLevel(); i++) { - writer.write('#'); + writer.raw('#'); } - writer.write(' '); + writer.raw(' '); visitChildren(heading); writer.block(); @@ -135,7 +135,7 @@ public void visit(IndentedCodeBlock indentedCodeBlock) { List lines = getLines(literal); for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); - writer.write(line); + writer.raw(line); if (i != lines.size() - 1) { writer.line(); } @@ -156,19 +156,19 @@ public void visit(FencedCodeBlock fencedCodeBlock) { writer.pushPrefix(indentPrefix); } - writer.write(fence); + writer.raw(fence); if (fencedCodeBlock.getInfo() != null) { - writer.write(fencedCodeBlock.getInfo()); + writer.raw(fencedCodeBlock.getInfo()); } writer.line(); if (!literal.isEmpty()) { List lines = getLines(literal); for (String line : lines) { - writer.write(line); + writer.raw(line); writer.line(); } } - writer.write(fence); + writer.raw(fence); if (indent > 0) { writer.popPrefix(); } @@ -180,7 +180,7 @@ public void visit(HtmlBlock htmlBlock) { List lines = getLines(htmlBlock.getLiteral()); for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); - writer.write(line); + writer.raw(line); if (i != lines.size() - 1) { writer.line(); } @@ -262,7 +262,7 @@ public void visit(Code code) { // If the literal includes backticks, we can surround them by using one more backtick. int backticks = findMaxRunLength('`', literal); for (int i = 0; i < backticks + 1; i++) { - writer.write('`'); + writer.raw('`'); } // If the literal starts or ends with a backtick, surround it with a single space. // If it starts and ends with a space (but is not only spaces), add an additional space (otherwise they would @@ -270,14 +270,14 @@ public void visit(Code code) { boolean addSpace = literal.startsWith("`") || literal.endsWith("`") || (literal.startsWith(" ") && literal.endsWith(" ") && Parsing.hasNonSpace(literal)); if (addSpace) { - writer.write(' '); + writer.raw(' '); } - writer.write(literal); + writer.raw(literal); if (addSpace) { - writer.write(' '); + writer.raw(' '); } for (int i = 0; i < backticks + 1; i++) { - writer.write('`'); + writer.raw('`'); } } @@ -285,16 +285,16 @@ public void visit(Code code) { public void visit(Emphasis emphasis) { // When emphasis is nested, a different delimiter needs to be used char delimiter = writer.getLastChar() == '*' ? '_' : '*'; - writer.write(delimiter); + writer.raw(delimiter); super.visit(emphasis); - writer.write(delimiter); + writer.raw(delimiter); } @Override public void visit(StrongEmphasis strongEmphasis) { - writer.write("**"); + writer.raw("**"); super.visit(strongEmphasis); - writer.write("**"); + writer.raw("**"); } @Override @@ -309,12 +309,12 @@ public void visit(Image image) { @Override public void visit(HtmlInline htmlInline) { - writer.write(htmlInline.getLiteral()); + writer.raw(htmlInline.getLiteral()); } @Override public void visit(HardLineBreak hardLineBreak) { - writer.write(" "); + writer.raw(" "); writer.line(); } @@ -339,19 +339,19 @@ public void visit(Text text) { switch (c) { case '-': { // Would be ambiguous with a bullet list marker, escape - writer.write("\\-"); + writer.raw("\\-"); literal = literal.substring(1); break; } case '#': { // Would be ambiguous with an ATX heading, escape - writer.write("\\#"); + writer.raw("\\#"); literal = literal.substring(1); break; } case '=': { // Would be ambiguous with a Setext heading, escape - writer.write("\\="); + writer.raw("\\="); literal = literal.substring(1); break; } @@ -368,19 +368,19 @@ public void visit(Text text) { // Check for ordered list marker Matcher m = orderedListMarkerPattern.matcher(literal); if (m.find()) { - writer.write(m.group(1)); - writer.write("\\" + m.group(2)); + writer.raw(m.group(1)); + writer.raw("\\" + m.group(2)); literal = literal.substring(m.end()); } break; } case '\t': { - writer.write(" "); + writer.raw(" "); literal = literal.substring(1); break; } case ' ': { - writer.write(" "); + writer.raw(" "); literal = literal.substring(1); break; } @@ -391,10 +391,10 @@ public void visit(Text text) { if (literal.endsWith("!") && text.getNext() instanceof Link) { // If we wrote the `!` unescaped, it would turn the link into an image instead. - writer.writeEscaped(literal.substring(0, literal.length() - 1), escape); - writer.write("\\!"); + writer.text(literal.substring(0, literal.length() - 1), escape); + writer.raw("\\!"); } else { - writer.writeEscaped(literal, escape); + writer.text(literal, escape); } } @@ -455,24 +455,24 @@ private static List getLines(String literal) { } private void writeLinkLike(String title, String destination, Node node, String opener) { - writer.write(opener); + writer.raw(opener); visitChildren(node); - writer.write(']'); - writer.write('('); + writer.raw(']'); + writer.raw('('); if (contains(destination, linkDestinationNeedsAngleBrackets)) { - writer.write('<'); - writer.writeEscaped(destination, linkDestinationEscapeInAngleBrackets); - writer.write('>'); + writer.raw('<'); + writer.text(destination, linkDestinationEscapeInAngleBrackets); + writer.raw('>'); } else { - writer.write(destination); + writer.raw(destination); } if (title != null) { - writer.write(' '); - writer.write('"'); - writer.writeEscaped(title, linkTitleEscapeInQuotes); - writer.write('"'); + writer.raw(' '); + writer.raw('"'); + writer.text(title, linkTitleEscapeInQuotes); + writer.raw('"'); } - writer.write(')'); + writer.raw(')'); } private static class ListHolder { diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index ef2945359..3b2ae18b0 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -5,6 +5,9 @@ import java.io.IOException; import java.util.LinkedList; +/** + * Writer for Markdown (CommonMark) text. + */ public class MarkdownWriter { private final Appendable buffer; @@ -19,28 +22,29 @@ public MarkdownWriter(Appendable out) { buffer = out; } - public char getLastChar() { - return lastChar; - } - /** - * @return whether we're at the line start (not counting any prefixes), i.e. after a {@link #line} or {@link #block}. + * Write the supplied string (raw/unescaped). */ - public boolean isAtLineStart() { - return atLineStart; - } - - public void write(String s) { + public void raw(String s) { flushBlockSeparator(); append(s); } - public void write(char c) { + /** + * Write the supplied character (raw/unescaped). + */ + public void raw(char c) { flushBlockSeparator(); append(c); } - public void writeEscaped(String s, CharMatcher escape) { + /** + * Write the supplied string with escaping. + * + * @param s the string to write + * @param escape which characters to escape + */ + public void text(String s, CharMatcher escape) { if (s.isEmpty()) { return; } @@ -66,6 +70,9 @@ public void writeEscaped(String s, CharMatcher escape) { atLineStart = false; } + /** + * Write a newline (line terminator). + */ public void line() { append('\n'); writePrefixes(); @@ -83,20 +90,67 @@ public void block() { atLineStart = true; } + /** + * Push a prefix onto the top of the stack. All prefixes are written at the beginning of each line, until the + * prefix is popped again. + * + * @param prefix the raw prefix string + */ public void pushPrefix(String prefix) { prefixes.addLast(prefix); } + /** + * Write a prefix. + * + * @param prefix the raw prefix string to write + */ public void writePrefix(String prefix) { boolean tmp = atLineStart; - write(prefix); + raw(prefix); atLineStart = tmp; } + /** + * Remove the last prefix from the top of the stack. + */ public void popPrefix() { prefixes.removeLast(); } + /** + * @return the last character that was written + */ + public char getLastChar() { + return lastChar; + } + + /** + * @return whether we're at the line start (not counting any prefixes), i.e. after a {@link #line} or {@link #block}. + */ + public boolean isAtLineStart() { + return atLineStart; + } + + /** + * @return whether blocks are currently set to tight or loose, see {@link #setTight(boolean)} + */ + public boolean getTight() { + return tight; + } + + /** + * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight + * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines + * within the list. + *

+ * Note that changing this does not affect block separators that have already been enqueued (with {@link #block()}, + * only future ones. + */ + public void setTight(boolean tight) { + this.tight = tight; + } + private void append(String s) { try { buffer.append(s); @@ -144,23 +198,4 @@ private void flushBlockSeparator() { blockSeparator = 0; } } - - /** - * @return whether blocks are currently set to tight or loose, see {@link #setTight(boolean)} - */ - public boolean getTight() { - return tight; - } - - /** - * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight - * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines - * within the list. - *

- * Note that changing this does not affect block separators that have already been enqueued (with {@link #block()}, - * only future ones. - */ - public void setTight(boolean tight) { - this.tight = tight; - } } From e6c82d533516553935b8f2a18d60314231cb8adb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 6 Feb 2024 12:44:26 +1100 Subject: [PATCH 038/345] Tables extension markdown renderer --- .../commonmark/ext/gfm/tables/TableCell.java | 2 +- .../ext/gfm/tables/TablesExtension.java | 16 +++- .../internal/TableMarkdownNodeRenderer.java | 96 +++++++++++++++++++ .../tables/TablesMarkdownRenderingTest.java | 70 ++++++++++++++ .../markdown/CoreMarkdownNodeRenderer.java | 8 +- .../renderer/markdown/MarkdownWriter.java | 94 ++++++++++++------ .../markdown/MarkdownRendererTest.java | 3 + 7 files changed, 255 insertions(+), 34 deletions(-) create mode 100644 commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java create mode 100644 commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesMarkdownRenderingTest.java diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java index 61880c6c3..bd7b40e02 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java @@ -22,7 +22,7 @@ public void setHeader(boolean header) { } /** - * @return the cell alignment + * @return the cell alignment or {@code null} if no specific alignment */ public Alignment getAlignment() { return alignment; diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java index 5707b0f14..d23f6f5fc 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java @@ -3,12 +3,16 @@ import org.commonmark.Extension; import org.commonmark.ext.gfm.tables.internal.TableBlockParser; import org.commonmark.ext.gfm.tables.internal.TableHtmlNodeRenderer; +import org.commonmark.ext.gfm.tables.internal.TableMarkdownNodeRenderer; import org.commonmark.ext.gfm.tables.internal.TableTextContentNodeRenderer; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; +import org.commonmark.renderer.markdown.MarkdownRenderer; import org.commonmark.renderer.text.TextContentNodeRendererContext; import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentRenderer; @@ -27,7 +31,7 @@ * @see Tables (extension) in GitHub Flavored Markdown Spec */ public class TablesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, - TextContentRenderer.TextContentRendererExtension { + TextContentRenderer.TextContentRendererExtension, MarkdownRenderer.MarkdownRendererExtension { private TablesExtension() { } @@ -60,4 +64,14 @@ public NodeRenderer create(TextContentNodeRendererContext context) { } }); } + + @Override + public void extend(MarkdownRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new TableMarkdownNodeRenderer(context); + } + }); + } } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java new file mode 100644 index 000000000..bf94db5bb --- /dev/null +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java @@ -0,0 +1,96 @@ +package org.commonmark.ext.gfm.tables.internal; + +import org.commonmark.ext.gfm.tables.*; +import org.commonmark.internal.util.AsciiMatcher; +import org.commonmark.internal.util.CharMatcher; +import org.commonmark.node.Node; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownWriter; + +import java.util.ArrayList; +import java.util.List; + +/** + * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to text content. + */ +public class TableMarkdownNodeRenderer extends TableNodeRenderer implements NodeRenderer { + private final MarkdownWriter writer; + private final MarkdownNodeRendererContext context; + + private final CharMatcher pipe = AsciiMatcher.builder().c('|').build(); + + private final List columns = new ArrayList<>(); + + public TableMarkdownNodeRenderer(MarkdownNodeRendererContext context) { + this.writer = context.getWriter(); + this.context = context; + } + + @Override + protected void renderBlock(TableBlock node) { + columns.clear(); + renderChildren(node); + writer.block(); + } + + @Override + protected void renderHead(TableHead node) { + renderChildren(node); + // TODO: Not sure about this.. Should block() detect if a line was already written? Or should line() itself be lazy? + writer.line(); + for (TableCell.Alignment columnAlignment : columns) { + writer.raw('|'); + if (columnAlignment == TableCell.Alignment.LEFT) { + writer.raw(":---"); + } else if (columnAlignment == TableCell.Alignment.RIGHT) { + writer.raw("---:"); + } else if (columnAlignment == TableCell.Alignment.CENTER) { + writer.raw(":---:"); + } else { + writer.raw("---"); + } + } + writer.raw("|"); + // TODO + if (node.getNext() != null) { + writer.line(); + } + } + + @Override + protected void renderBody(TableBody node) { + renderChildren(node); + } + + @Override + protected void renderRow(TableRow node) { + renderChildren(node); + // Trailing | at the end of the line + writer.raw("|"); + // TODO + if (node.getNext() != null) { + writer.line(); + } + } + + @Override + protected void renderCell(TableCell node) { + if (node.getParent() != null && node.getParent().getParent() instanceof TableHead) { + columns.add(node.getAlignment()); + } + writer.raw("|"); + writer.pushRawEscape(pipe); + renderChildren(node); + writer.popRawEscape(); + } + + private void renderChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } +} diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesMarkdownRenderingTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesMarkdownRenderingTest.java new file mode 100644 index 000000000..8689e4ac2 --- /dev/null +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesMarkdownRenderingTest.java @@ -0,0 +1,70 @@ +package org.commonmark.ext.gfm.tables; + +import org.commonmark.Extension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.markdown.MarkdownRenderer; +import org.junit.Test; + +import java.util.Collections; +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +public class TablesMarkdownRenderingTest { + + private static final Set EXTENSIONS = Collections.singleton(TablesExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void testHeadNoBody() { + assertRoundTrip("|Abc|\n|---|\n"); + assertRoundTrip("|Abc|Def|\n|---|---|\n"); + assertRoundTrip("|Abc||\n|---|---|\n"); + } + + @Test + public void testHeadAndBody() { + assertRoundTrip("|Abc|\n|---|\n|1|\n"); + assertRoundTrip("|Abc|Def|\n|---|---|\n|1|2|\n"); + } + + @Test + public void testBodyHasFewerColumns() { + // Could try not to write empty trailing cells but this is fine too + assertRoundTrip("|Abc|Def|\n|---|---|\n|1||\n"); + } + + @Test + public void testAlignment() { + assertRoundTrip("|Abc|Def|\n|:---|---|\n|1|2|\n"); + assertRoundTrip("|Abc|Def|\n|---|---:|\n|1|2|\n"); + assertRoundTrip("|Abc|Def|\n|:---:|:---:|\n|1|2|\n"); + } + + @Test + public void testInsideBlockQuote() { + assertRoundTrip("> |Abc|Def|\n> |---|---|\n> |1|2|\n"); + } + + @Test + public void testMultipleTables() { + assertRoundTrip("|Abc|Def|\n|---|---|\n\n|One|\n|---|\n|Only|\n"); + } + + @Test + public void testEscaping() { + assertRoundTrip("|Abc|Def|\n|---|---|\n|Pipe in|text \\||\n"); + assertRoundTrip("|Abc|Def|\n|---|---|\n|Pipe in|code `\\|`|\n"); + assertRoundTrip("|Abc|Def|\n|---|---|\n|Inline HTML|Foo\\|bar|\n"); + } + + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } + + private void assertRoundTrip(String input) { + String rendered = render(input); + assertEquals(input, rendered); + } +} diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index f5a9377f6..714144e89 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -23,15 +23,15 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer { private final AsciiMatcher textEscape = - AsciiMatcher.builder().anyOf("[]<>`*&").build(); + AsciiMatcher.builder().anyOf("[]<>`*&\n\\").build(); private final CharMatcher textEscapeInHeading = AsciiMatcher.builder(textEscape).anyOf("#").build(); private final CharMatcher linkDestinationNeedsAngleBrackets = - AsciiMatcher.builder().c(' ').c('(').c(')').c('<').c('>').c('\\').build(); + AsciiMatcher.builder().c(' ').c('(').c(')').c('<').c('>').c('\n').c('\\').build(); private final CharMatcher linkDestinationEscapeInAngleBrackets = - AsciiMatcher.builder().c('<').c('>').build(); + AsciiMatcher.builder().c('<').c('>').c('\n').c('\\').build(); private final CharMatcher linkTitleEscapeInQuotes = - AsciiMatcher.builder().c('"').build(); + AsciiMatcher.builder().c('"').c('\n').c('\\').build(); private final Pattern orderedListMarkerPattern = Pattern.compile("^([0-9]{1,9})([.)])"); diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index 3b2ae18b0..f648d9c4f 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -17,25 +17,26 @@ public class MarkdownWriter { private char lastChar; private boolean atLineStart = true; private final LinkedList prefixes = new LinkedList<>(); + private final LinkedList rawEscapes = new LinkedList<>(); public MarkdownWriter(Appendable out) { buffer = out; } /** - * Write the supplied string (raw/unescaped). + * Write the supplied string (raw/unescaped except if {@link #pushRawEscape} was used). */ public void raw(String s) { flushBlockSeparator(); - append(s); + write(s, null); } /** - * Write the supplied character (raw/unescaped). + * Write the supplied character (raw/unescaped except if {@link #pushRawEscape} was used). */ public void raw(char c) { flushBlockSeparator(); - append(c); + write(c); } /** @@ -49,22 +50,7 @@ public void text(String s, CharMatcher escape) { return; } flushBlockSeparator(); - try { - for (int i = 0; i < s.length(); i++) { - char ch = s.charAt(i); - if (ch == '\n') { - // Can't escape this with \, use numeric character reference - buffer.append(" "); - } else { - if (ch == '\\' || escape.matches(ch)) { - buffer.append('\\'); - } - buffer.append(ch); - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } + write(s, escape); lastChar = s.charAt(s.length() - 1); atLineStart = false; @@ -74,7 +60,7 @@ public void text(String s, CharMatcher escape) { * Write a newline (line terminator). */ public void line() { - append('\n'); + write('\n'); writePrefixes(); atLineStart = true; } @@ -118,6 +104,24 @@ public void popPrefix() { prefixes.removeLast(); } + /** + * Escape the characters matching the supplied matcher, in all text (text and raw). This might be useful to + * extensions that add another layer of syntax, e.g. the tables extension that uses `|` to separate cells and needs + * all `|` characters to be escaped (even in code spans). + * + * @param rawEscape the characters to escape in raw text + */ + public void pushRawEscape(CharMatcher rawEscape) { + rawEscapes.add(rawEscape); + } + + /** + * Remove the last raw escape from the top of the stack. + */ + public void popRawEscape() { + rawEscapes.removeLast(); + } + /** * @return the last character that was written */ @@ -151,9 +155,16 @@ public void setTight(boolean tight) { this.tight = tight; } - private void append(String s) { + private void write(String s, CharMatcher escape) { try { - buffer.append(s); + if (rawEscapes.isEmpty() && escape == null) { + // Normal fast path + buffer.append(s); + } else { + for (int i = 0; i < s.length(); i++) { + append(s.charAt(i), escape); + } + } } catch (IOException e) { throw new RuntimeException(e); } @@ -165,9 +176,9 @@ private void append(String s) { atLineStart = false; } - private void append(char c) { + private void write(char c) { try { - buffer.append(c); + append(c, null); } catch (IOException e) { throw new RuntimeException(e); } @@ -179,7 +190,7 @@ private void append(char c) { private void writePrefixes() { if (!prefixes.isEmpty()) { for (String prefix : prefixes) { - append(prefix); + write(prefix, null); } } } @@ -189,13 +200,40 @@ private void writePrefixes() { */ private void flushBlockSeparator() { if (blockSeparator != 0) { - append('\n'); + write('\n'); writePrefixes(); if (blockSeparator > 1) { - append('\n'); + write('\n'); writePrefixes(); } blockSeparator = 0; } } + + private void append(char c, CharMatcher escape) throws IOException { + if (needsEscaping(c, escape)) { + if (c == '\n') { + // Can't escape this with \, use numeric character reference + buffer.append(" "); + } else { + buffer.append('\\'); + buffer.append(c); + } + } else { + buffer.append(c); + } + } + + private boolean needsEscaping(char c, CharMatcher escape) { + return (escape != null && escape.matches(c)) || rawNeedsEscaping(c); + } + + private boolean rawNeedsEscaping(char c) { + for (CharMatcher rawEscape : rawEscapes) { + if (rawEscape.matches(c)) { + return true; + } + } + return false; + } } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index bc6d9b696..20453eed7 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -194,6 +194,9 @@ public void testLinks() { assertRoundTrip("[a](c>)\n"); assertRoundTrip("[a](c>)\n"); assertRoundTrip("[a](/uri \"foo \\\" bar\")\n"); + assertRoundTrip("[link](/uri \"tes\\\\\")\n"); + assertRoundTrip("[link](/url \"test \")\n"); + assertRoundTrip("[link]()\n"); } @Test From d726f72916bcdcdc3b5fb8d49d97beb6aaf784c2 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 6 Feb 2024 12:52:43 +1100 Subject: [PATCH 039/345] Change isTight/setTight to use push/pop pattern like other settings --- .../markdown/CoreMarkdownNodeRenderer.java | 10 ++-- .../renderer/markdown/MarkdownWriter.java | 49 +++++++++++-------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 714144e89..c7fa9be7a 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -205,23 +205,21 @@ public void visit(BlockQuote blockQuote) { @Override public void visit(BulletList bulletList) { - boolean oldTight = writer.getTight(); - writer.setTight(bulletList.isTight()); + writer.pushTight(bulletList.isTight()); listHolder = new BulletListHolder(listHolder, bulletList); visitChildren(bulletList); listHolder = listHolder.parent; - writer.setTight(oldTight); + writer.popTight(); writer.block(); } @Override public void visit(OrderedList orderedList) { - boolean oldTight = writer.getTight(); - writer.setTight(orderedList.isTight()); + writer.pushTight(orderedList.isTight()); listHolder = new OrderedListHolder(listHolder, orderedList); visitChildren(orderedList); listHolder = listHolder.parent; - writer.setTight(oldTight); + writer.popTight(); writer.block(); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index f648d9c4f..1231a4a73 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -13,10 +13,13 @@ public class MarkdownWriter { private final Appendable buffer; private int blockSeparator = 0; - private boolean tight; private char lastChar; private boolean atLineStart = true; + + // Stacks of settings that affect various rendering behaviors. The common pattern here is that callers use "push" to + // change a setting, render some nodes, and then "pop" the setting off the stack again to restore previous state. private final LinkedList prefixes = new LinkedList<>(); + private final LinkedList tight = new LinkedList<>(); private final LinkedList rawEscapes = new LinkedList<>(); public MarkdownWriter(Appendable out) { @@ -72,7 +75,7 @@ public void line() { public void block() { // Remember whether this should be a tight or loose separator now because tight could get changed in between // this and the next flush. - blockSeparator = tight ? 1 : 2; + blockSeparator = isTight() ? 1 : 2; atLineStart = true; } @@ -104,6 +107,25 @@ public void popPrefix() { prefixes.removeLast(); } + /** + * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight + * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines + * within the list. + *

+ * Note that changing this does not affect block separators that have already been enqueued with {@link #block()}, + * only future ones. + */ + public void pushTight(boolean tight) { + this.tight.addLast(tight); + } + + /** + * Remove the last "tight" setting from the top of the stack. + */ + public void popTight() { + this.tight.removeLast(); + } + /** * Escape the characters matching the supplied matcher, in all text (text and raw). This might be useful to * extensions that add another layer of syntax, e.g. the tables extension that uses `|` to separate cells and needs @@ -136,25 +158,6 @@ public boolean isAtLineStart() { return atLineStart; } - /** - * @return whether blocks are currently set to tight or loose, see {@link #setTight(boolean)} - */ - public boolean getTight() { - return tight; - } - - /** - * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight - * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines - * within the list. - *

- * Note that changing this does not affect block separators that have already been enqueued (with {@link #block()}, - * only future ones. - */ - public void setTight(boolean tight) { - this.tight = tight; - } - private void write(String s, CharMatcher escape) { try { if (rawEscapes.isEmpty() && escape == null) { @@ -224,6 +227,10 @@ private void append(char c, CharMatcher escape) throws IOException { } } + private boolean isTight() { + return !tight.isEmpty() && tight.getLast(); + } + private boolean needsEscaping(char c, CharMatcher escape) { return (escape != null && escape.matches(c)) || rawNeedsEscaping(c); } From aaf18a2974d55bb9d0c83ce97e97e1439ff27dc2 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 6 Feb 2024 12:58:30 +1100 Subject: [PATCH 040/345] Use tight blocks to render tables --- .../tables/internal/TableMarkdownNodeRenderer.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java index bf94db5bb..487147164 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java @@ -30,15 +30,15 @@ public TableMarkdownNodeRenderer(MarkdownNodeRendererContext context) { @Override protected void renderBlock(TableBlock node) { columns.clear(); + writer.pushTight(true); renderChildren(node); + writer.popTight(); writer.block(); } @Override protected void renderHead(TableHead node) { renderChildren(node); - // TODO: Not sure about this.. Should block() detect if a line was already written? Or should line() itself be lazy? - writer.line(); for (TableCell.Alignment columnAlignment : columns) { writer.raw('|'); if (columnAlignment == TableCell.Alignment.LEFT) { @@ -52,10 +52,7 @@ protected void renderHead(TableHead node) { } } writer.raw("|"); - // TODO - if (node.getNext() != null) { - writer.line(); - } + writer.block(); } @Override @@ -68,10 +65,7 @@ protected void renderRow(TableRow node) { renderChildren(node); // Trailing | at the end of the line writer.raw("|"); - // TODO - if (node.getNext() != null) { - writer.line(); - } + writer.block(); } @Override From 01a52bce23dd7efb1a31a772d086b34fd9930b0d Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 6 Feb 2024 22:04:50 +1100 Subject: [PATCH 041/345] Rename file --- ...arkdownRenderingTest.java => TableMarkdownRendererTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/{TablesMarkdownRenderingTest.java => TableMarkdownRendererTest.java} (98%) diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesMarkdownRenderingTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java similarity index 98% rename from commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesMarkdownRenderingTest.java rename to commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java index 8689e4ac2..cf63fb19c 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesMarkdownRenderingTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java @@ -10,7 +10,7 @@ import static org.junit.Assert.assertEquals; -public class TablesMarkdownRenderingTest { +public class TableMarkdownRendererTest { private static final Set EXTENSIONS = Collections.singleton(TablesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); From a490faee954641b6a8d8bacd754f832c4eb646ec Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 6 Feb 2024 22:31:13 +1100 Subject: [PATCH 042/345] Strikethrough markdown renderer --- .../ext/gfm/strikethrough/Strikethrough.java | 12 ++++--- .../strikethrough/StrikethroughExtension.java | 30 +++++++++++----- .../StrikethroughDelimiterProcessor.java | 3 +- .../StrikethroughMarkdownNodeRenderer.java | 34 ++++++++++++++++++ .../StrikethroughMarkdownRendererTest.java | 36 +++++++++++++++++++ 5 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughMarkdownNodeRenderer.java create mode 100644 commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/Strikethrough.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/Strikethrough.java index 115ae9ea4..0c24642bc 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/Strikethrough.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/Strikethrough.java @@ -4,19 +4,23 @@ import org.commonmark.node.Delimited; /** - * A strikethrough node containing text and other inline nodes nodes as children. + * A strikethrough node containing text and other inline nodes as children. */ public class Strikethrough extends CustomNode implements Delimited { - private static final String DELIMITER = "~~"; + private String delimiter; + + public Strikethrough(String delimiter) { + this.delimiter = delimiter; + } @Override public String getOpeningDelimiter() { - return DELIMITER; + return delimiter; } @Override public String getClosingDelimiter() { - return DELIMITER; + return delimiter; } } diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java index 4f0228a1c..aa7dff716 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java @@ -1,17 +1,21 @@ package org.commonmark.ext.gfm.strikethrough; import org.commonmark.Extension; -import org.commonmark.renderer.text.TextContentRenderer; -import org.commonmark.renderer.text.TextContentNodeRendererContext; -import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughDelimiterProcessor; import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughHtmlNodeRenderer; +import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughMarkdownNodeRenderer; import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughTextContentNodeRenderer; -import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlNodeRendererFactory; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; +import org.commonmark.renderer.markdown.MarkdownRenderer; +import org.commonmark.renderer.text.TextContentNodeRendererContext; +import org.commonmark.renderer.text.TextContentNodeRendererFactory; +import org.commonmark.renderer.text.TextContentRenderer; /** * Extension for GFM strikethrough using {@code ~} or {@code ~~} (GitHub Flavored Markdown). @@ -42,7 +46,7 @@ *

*/ public class StrikethroughExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, - TextContentRenderer.TextContentRendererExtension { + TextContentRenderer.TextContentRendererExtension, MarkdownRenderer.MarkdownRendererExtension { private final boolean requireTwoTildes; @@ -89,13 +93,23 @@ public NodeRenderer create(TextContentNodeRendererContext context) { }); } + @Override + public void extend(MarkdownRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new StrikethroughMarkdownNodeRenderer(context); + } + }); + } + public static class Builder { private boolean requireTwoTildes = false; /** * @param requireTwoTildes Whether two tilde characters ({@code ~~}) are required for strikethrough or whether - * one is also enough. Default is {@code false}; both a single tilde and two tildes can be used for strikethrough. + * one is also enough. Default is {@code false}; both a single tilde and two tildes can be used for strikethrough. * @return {@code this} */ public Builder requireTwoTildes(boolean requireTwoTildes) { diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java index 3dedff1b9..4657106ab 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java @@ -43,7 +43,8 @@ public int process(DelimiterRun openingRun, DelimiterRun closingRun) { Text opener = openingRun.getOpener(); // Wrap nodes between delimiters in strikethrough. - Node strikethrough = new Strikethrough(); + String delimiter = openingRun.length() == 1 ? opener.getLiteral() : opener.getLiteral() + opener.getLiteral(); + Node strikethrough = new Strikethrough(delimiter); SourceSpans sourceSpans = new SourceSpans(); sourceSpans.addAllFrom(openingRun.getOpeners(openingRun.length())); diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughMarkdownNodeRenderer.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughMarkdownNodeRenderer.java new file mode 100644 index 000000000..1c91dd64f --- /dev/null +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughMarkdownNodeRenderer.java @@ -0,0 +1,34 @@ +package org.commonmark.ext.gfm.strikethrough.internal; + +import org.commonmark.ext.gfm.strikethrough.Strikethrough; +import org.commonmark.node.Node; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownWriter; + +public class StrikethroughMarkdownNodeRenderer extends StrikethroughNodeRenderer { + + private final MarkdownNodeRendererContext context; + private final MarkdownWriter writer; + + public StrikethroughMarkdownNodeRenderer(MarkdownNodeRendererContext context) { + this.context = context; + this.writer = context.getWriter(); + } + + @Override + public void render(Node node) { + Strikethrough strikethrough = (Strikethrough) node; + writer.raw(strikethrough.getOpeningDelimiter()); + renderChildren(node); + writer.raw(strikethrough.getClosingDelimiter()); + } + + private void renderChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } +} diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java new file mode 100644 index 000000000..b722018b8 --- /dev/null +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java @@ -0,0 +1,36 @@ +package org.commonmark.ext.gfm.strikethrough; + +import org.commonmark.Extension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.markdown.MarkdownRenderer; +import org.junit.Test; + +import java.util.Collections; +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +public class StrikethroughMarkdownRendererTest { + + private static final Set EXTENSIONS = Collections.singleton(StrikethroughExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void testStrikethrough() { + assertRoundTrip("~foo~ ~bar~\n"); + assertRoundTrip("~~f~oo~~ ~~bar~~\n"); + + // TODO this new special character needs to be escaped: +// assertRoundTrip("\\~foo\\~\n"); + } + + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } + + private void assertRoundTrip(String input) { + String rendered = render(input); + assertEquals(input, rendered); + } +} From 981d2363851da3fa81bd7ec333d237604b823235 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 6 Feb 2024 23:04:37 +1100 Subject: [PATCH 043/345] Add getSpecialCharacters for escaping of extension characters --- .../strikethrough/StrikethroughExtension.java | 8 +++++++ .../StrikethroughMarkdownRendererTest.java | 6 +++--- .../ext/gfm/tables/TablesExtension.java | 8 +++++++ .../internal/util/AsciiMatcher.java | 16 ++++++++++---- .../markdown/CoreMarkdownNodeRenderer.java | 9 ++++---- .../markdown/MarkdownNodeRendererContext.java | 8 +++++++ .../markdown/MarkdownNodeRendererFactory.java | 8 +++++++ .../renderer/markdown/MarkdownRenderer.java | 21 +++++++++++++++++-- 8 files changed, 71 insertions(+), 13 deletions(-) diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java index aa7dff716..f87f3e9c8 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java @@ -17,6 +17,9 @@ import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentRenderer; +import java.util.Collections; +import java.util.Set; + /** * Extension for GFM strikethrough using {@code ~} or {@code ~~} (GitHub Flavored Markdown). *

Example input:

@@ -100,6 +103,11 @@ public void extend(MarkdownRenderer.Builder rendererBuilder) { public NodeRenderer create(MarkdownNodeRendererContext context) { return new StrikethroughMarkdownNodeRenderer(context); } + + @Override + public Set getSpecialCharacters() { + return Collections.singleton('~'); + } }); } diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java index b722018b8..96df48cec 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java @@ -19,10 +19,10 @@ public class StrikethroughMarkdownRendererTest { @Test public void testStrikethrough() { assertRoundTrip("~foo~ ~bar~\n"); - assertRoundTrip("~~f~oo~~ ~~bar~~\n"); + assertRoundTrip("~~foo~~ ~~bar~~\n"); + assertRoundTrip("~~f\\~oo~~ ~~bar~~\n"); - // TODO this new special character needs to be escaped: -// assertRoundTrip("\\~foo\\~\n"); + assertRoundTrip("\\~foo\\~\n"); } protected String render(String source) { diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java index d23f6f5fc..92e1f0ba4 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java @@ -17,6 +17,9 @@ import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentRenderer; +import java.util.Collections; +import java.util.Set; + /** * Extension for GFM tables using "|" pipes (GitHub Flavored Markdown). *

@@ -72,6 +75,11 @@ public void extend(MarkdownRenderer.Builder rendererBuilder) { public NodeRenderer create(MarkdownNodeRendererContext context) { return new TableMarkdownNodeRenderer(context); } + + @Override + public Set getSpecialCharacters() { + return Collections.emptySet(); + } }); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java b/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java index d31020fa3..dd7e8d5eb 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java @@ -1,6 +1,7 @@ package org.commonmark.internal.util; import java.util.BitSet; +import java.util.Set; public class AsciiMatcher implements CharMatcher { private final BitSet set; @@ -33,6 +34,14 @@ private Builder(BitSet set) { this.set = set; } + public Builder c(char c) { + if (c > 127) { + throw new IllegalArgumentException("Can only match ASCII characters"); + } + set.set(c); + return this; + } + public Builder anyOf(String s) { for (int i = 0; i < s.length(); i++) { c(s.charAt(i)); @@ -40,11 +49,10 @@ public Builder anyOf(String s) { return this; } - public Builder c(char c) { - if (c > 127) { - throw new IllegalArgumentException("Can only match ASCII characters"); + public Builder anyOf(Set characters) { + for (Character c : characters) { + c(c); } - set.set(c); return this; } diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index c7fa9be7a..2db7ef30c 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -22,10 +22,8 @@ */ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer { - private final AsciiMatcher textEscape = - AsciiMatcher.builder().anyOf("[]<>`*&\n\\").build(); - private final CharMatcher textEscapeInHeading = - AsciiMatcher.builder(textEscape).anyOf("#").build(); + private final AsciiMatcher textEscape; + private final CharMatcher textEscapeInHeading; private final CharMatcher linkDestinationNeedsAngleBrackets = AsciiMatcher.builder().c(' ').c('(').c(')').c('<').c('>').c('\n').c('\\').build(); private final CharMatcher linkDestinationEscapeInAngleBrackets = @@ -46,6 +44,9 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen public CoreMarkdownNodeRenderer(MarkdownNodeRendererContext context) { this.context = context; this.writer = context.getWriter(); + + textEscape = AsciiMatcher.builder().anyOf("[]<>`*&\n\\").anyOf(context.getSpecialCharacters()).build(); + textEscapeInHeading = AsciiMatcher.builder(textEscape).anyOf("#").build(); } @Override diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java index 8fe0f73d5..5805c458b 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java @@ -2,6 +2,8 @@ import org.commonmark.node.Node; +import java.util.Set; + public interface MarkdownNodeRendererContext { /** @@ -16,4 +18,10 @@ public interface MarkdownNodeRendererContext { * @param node the node to render */ void render(Node node); + + /** + * @return additional special characters that need to be escaped if they occur in normal text; currently only ASCII + * characters are allowed + */ + Set getSpecialCharacters(); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java index 7b3134277..adfe8a07b 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java @@ -2,6 +2,8 @@ import org.commonmark.renderer.NodeRenderer; +import java.util.Set; + /** * Factory for instantiating new node renderers ƒor rendering. */ @@ -14,4 +16,10 @@ public interface MarkdownNodeRendererFactory { * @return a node renderer */ NodeRenderer create(MarkdownNodeRendererContext context); + + /** + * @return the additional special characters that this factory would like to have escaped in normal text; currently + * only ASCII characters are allowed + */ + Set getSpecialCharacters(); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java index 4dee17ed6..926105202 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java @@ -6,8 +6,7 @@ import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.Renderer; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * Renders nodes to CommonMark Markdown. @@ -32,6 +31,11 @@ private MarkdownRenderer(Builder builder) { public NodeRenderer create(MarkdownNodeRendererContext context) { return new CoreMarkdownNodeRenderer(context); } + + @Override + public Set getSpecialCharacters() { + return Collections.emptySet(); + } }); } @@ -111,13 +115,21 @@ public interface MarkdownRendererExtension extends Extension { private class RendererContext implements MarkdownNodeRendererContext { private final MarkdownWriter writer; private final NodeRendererMap nodeRendererMap = new NodeRendererMap(); + private final Set additionalTextEscapes; private RendererContext(MarkdownWriter writer) { + // Set fields that are used by interface this.writer = writer; + Set escapes = new HashSet(); + for (MarkdownNodeRendererFactory factory : nodeRendererFactories) { + escapes.addAll(factory.getSpecialCharacters()); + } + additionalTextEscapes = Collections.unmodifiableSet(escapes); // The first node renderer for a node type "wins". for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) { MarkdownNodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i); + // Pass in this as context here, which uses the fields set above NodeRenderer nodeRenderer = nodeRendererFactory.create(this); nodeRendererMap.add(nodeRenderer); } @@ -132,5 +144,10 @@ public MarkdownWriter getWriter() { public void render(Node node) { nodeRendererMap.render(node); } + + @Override + public Set getSpecialCharacters() { + return additionalTextEscapes; + } } } From 64eafc413dfc5bc936dd1ae7dbf189d46a8652fa Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 6 Feb 2024 23:06:42 +1100 Subject: [PATCH 044/345] Use getSpecialCharacters for table extension too --- .../java/org/commonmark/ext/gfm/tables/TablesExtension.java | 2 +- .../ext/gfm/tables/TableMarkdownRendererTest.java | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java index 92e1f0ba4..d18c38283 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java @@ -78,7 +78,7 @@ public NodeRenderer create(MarkdownNodeRendererContext context) { @Override public Set getSpecialCharacters() { - return Collections.emptySet(); + return Collections.singleton('|'); } }); } diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java index cf63fb19c..1db917d08 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java @@ -59,6 +59,12 @@ public void testEscaping() { assertRoundTrip("|Abc|Def|\n|---|---|\n|Inline HTML|Foo\\|bar|\n"); } + @Test + public void testEscaped() { + // `|` in Text nodes needs to be escaped, otherwise the generated Markdown does not get parsed back as a table + assertRoundTrip("\\|Abc\\|\n\\|---\\|\n"); + } + protected String render(String source) { return RENDERER.render(PARSER.parse(source)); } From 2966df1113eb28c5a3940718e62a25a75a24fd69 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 7 Feb 2024 19:13:55 +1100 Subject: [PATCH 045/345] Ins extensions markdown renderer --- .../org/commonmark/ext/ins/InsExtension.java | 28 +++++++++++++-- .../ins/internal/InsMarkdownNodeRenderer.java | 32 +++++++++++++++++ .../ext/ins/InsMarkdownRendererTest.java | 34 +++++++++++++++++++ 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsMarkdownNodeRenderer.java create mode 100644 commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java index 2f980d93b..065719558 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java @@ -3,16 +3,23 @@ import org.commonmark.Extension; import org.commonmark.ext.ins.internal.InsDelimiterProcessor; import org.commonmark.ext.ins.internal.InsHtmlNodeRenderer; +import org.commonmark.ext.ins.internal.InsMarkdownNodeRenderer; import org.commonmark.ext.ins.internal.InsTextContentNodeRenderer; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; +import org.commonmark.renderer.markdown.MarkdownRenderer; import org.commonmark.renderer.text.TextContentNodeRendererContext; import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentRenderer; +import java.util.Collections; +import java.util.Set; + /** * Extension for ins using ++ *

@@ -24,9 +31,7 @@ * The parsed ins text regions are turned into {@link Ins} nodes. *

*/ -public class InsExtension implements Parser.ParserExtension, - HtmlRenderer.HtmlRendererExtension, - TextContentRenderer.TextContentRendererExtension { +public class InsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, TextContentRenderer.TextContentRendererExtension, MarkdownRenderer.MarkdownRendererExtension { private InsExtension() { } @@ -59,4 +64,21 @@ public NodeRenderer create(TextContentNodeRendererContext context) { } }); } + + @Override + public void extend(MarkdownRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new InsMarkdownNodeRenderer(context); + } + + @Override + public Set getSpecialCharacters() { + // We technically don't need to escape single occurrences of +, but that's all the extension API + // exposes currently. + return Collections.singleton('+'); + } + }); + } } diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsMarkdownNodeRenderer.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsMarkdownNodeRenderer.java new file mode 100644 index 000000000..851d47282 --- /dev/null +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsMarkdownNodeRenderer.java @@ -0,0 +1,32 @@ +package org.commonmark.ext.ins.internal; + +import org.commonmark.node.Node; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownWriter; + +public class InsMarkdownNodeRenderer extends InsNodeRenderer { + + private final MarkdownNodeRendererContext context; + private final MarkdownWriter writer; + + public InsMarkdownNodeRenderer(MarkdownNodeRendererContext context) { + this.context = context; + this.writer = context.getWriter(); + } + + @Override + public void render(Node node) { + writer.raw("++"); + renderChildren(node); + writer.raw("++"); + } + + private void renderChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } +} diff --git a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java new file mode 100644 index 000000000..16cefc7f1 --- /dev/null +++ b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java @@ -0,0 +1,34 @@ +package org.commonmark.ext.ins; + +import org.commonmark.Extension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.markdown.MarkdownRenderer; +import org.junit.Test; + +import java.util.Collections; +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +public class InsMarkdownRendererTest { + + private static final Set EXTENSIONS = Collections.singleton(InsExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void testStrikethrough() { + assertRoundTrip("++foo++\n"); + + assertRoundTrip("\\+\\+foo\\+\\+\n"); + } + + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } + + private void assertRoundTrip(String input) { + String rendered = render(input); + assertEquals(input, rendered); + } +} From 0c5ad6e1f16ae2b655db93fd07c0af502fead16e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 8 Feb 2024 23:26:03 +1100 Subject: [PATCH 046/345] Use parsed delimiter for emphasis if available --- .../markdown/CoreMarkdownNodeRenderer.java | 10 ++++-- .../markdown/MarkdownRendererTest.java | 34 ++++++++++++++----- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 2db7ef30c..748ff5dfb 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -45,7 +45,7 @@ public CoreMarkdownNodeRenderer(MarkdownNodeRendererContext context) { this.context = context; this.writer = context.getWriter(); - textEscape = AsciiMatcher.builder().anyOf("[]<>`*&\n\\").anyOf(context.getSpecialCharacters()).build(); + textEscape = AsciiMatcher.builder().anyOf("[]<>`*_&\n\\").anyOf(context.getSpecialCharacters()).build(); textEscapeInHeading = AsciiMatcher.builder(textEscape).anyOf("#").build(); } @@ -282,8 +282,12 @@ public void visit(Code code) { @Override public void visit(Emphasis emphasis) { - // When emphasis is nested, a different delimiter needs to be used - char delimiter = writer.getLastChar() == '*' ? '_' : '*'; + String delimiter = emphasis.getOpeningDelimiter(); + // Use delimiter that was parsed if available + if (delimiter == null) { + // When emphasis is nested, a different delimiter needs to be used + delimiter = writer.getLastChar() == '*' ? "_" : "*"; + } writer.raw(delimiter); super.visit(emphasis); writer.raw(delimiter); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 20453eed7..af6a3488a 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -1,6 +1,6 @@ package org.commonmark.renderer.markdown; -import org.commonmark.node.Node; +import org.commonmark.node.*; import org.commonmark.parser.Parser; import org.junit.Test; @@ -173,9 +173,21 @@ public void testEmphasis() { // When nesting, a different delimiter needs to be used assertRoundTrip("*_foo_*\n"); assertRoundTrip("*_*foo*_*\n"); + assertRoundTrip("_*foo*_\n"); // Not emphasis (needs * inside words) - assertRoundTrip("foo_bar_\n"); + assertRoundTrip("foo\\_bar\\_\n"); + + // Even when rendering a manually constructed tree, the emphasis delimiter needs to be chosen correctly. + Document doc = new Document(); + Paragraph p = new Paragraph(); + doc.appendChild(p); + Emphasis e1 = new Emphasis(); + p.appendChild(e1); + Emphasis e2 = new Emphasis(); + e1.appendChild(e2); + e2.appendChild(new Text("hi")); + assertEquals("*_hi_*\n", render(doc)); } @Test @@ -226,17 +238,21 @@ public void testSoftLineBreaks() { assertRoundTrip("foo\nbar\n"); } - private Node parse(String source) { - return Parser.builder().build().parse(source); + private void assertRoundTrip(String input) { + String rendered = parseAndRender(input); + assertEquals(input, rendered); } - private String render(String source) { + private String parseAndRender(String source) { Node parsed = parse(source); - return MarkdownRenderer.builder().build().render(parsed); + return render(parsed); } - private void assertRoundTrip(String input) { - String rendered = render(input); - assertEquals(input, rendered); + private Node parse(String source) { + return Parser.builder().build().parse(source); + } + + private String render(Node node) { + return MarkdownRenderer.builder().build().render(node); } } From 5a0960f9903ffef5b3b488969fd922c6e2067b4d Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 8 Feb 2024 23:26:32 +1100 Subject: [PATCH 047/345] Add integration test for multiple extensions --- .../MarkdownRendererIntegrationTest.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java new file mode 100644 index 000000000..51f761cfd --- /dev/null +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java @@ -0,0 +1,46 @@ +package org.commonmark.integration; + +import org.commonmark.Extension; +import org.commonmark.ext.autolink.AutolinkExtension; +import org.commonmark.ext.front.matter.YamlFrontMatterExtension; +import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; +import org.commonmark.ext.gfm.tables.TablesExtension; +import org.commonmark.ext.image.attributes.ImageAttributesExtension; +import org.commonmark.ext.ins.InsExtension; +import org.commonmark.ext.task.list.items.TaskListItemsExtension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.markdown.MarkdownRenderer; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class MarkdownRendererIntegrationTest { + + private static final List EXTENSIONS = Arrays.asList( + AutolinkExtension.create(), + ImageAttributesExtension.create(), + InsExtension.create(), + StrikethroughExtension.create(), + TablesExtension.create(), + TaskListItemsExtension.create(), + YamlFrontMatterExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void testStrikethroughInTable() { + assertRoundTrip("|Abc|\n|---|\n|~strikethrough~|\n|\\~escaped\\~|\n"); + } + + private String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } + + private void assertRoundTrip(String input) { + String rendered = render(input); + assertEquals(input, rendered); + } +} From 114956af772cb82aa8f5c262e735aadcb0adc9cf Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 8 Feb 2024 23:31:37 +1100 Subject: [PATCH 048/345] Fix thematic break and list items --- .../renderer/markdown/CoreMarkdownNodeRenderer.java | 3 ++- .../renderer/markdown/MarkdownRendererTest.java | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 748ff5dfb..30d6d1a5c 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -89,7 +89,8 @@ public void visit(Document document) { @Override public void visit(ThematicBreak thematicBreak) { - writer.raw("***"); + // Let's use ___ as it doesn't introduce ambiguity with * or - list item markers + writer.raw("___"); writer.block(); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index af6a3488a..eaabe837c 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -12,10 +12,11 @@ public class MarkdownRendererTest { @Test public void testThematicBreaks() { - assertRoundTrip("***\n"); - // TODO: spec: If you want a thematic break in a list item, use a different bullet: - - assertRoundTrip("***\n\nfoo\n"); + assertRoundTrip("___\n"); + assertRoundTrip("___\n\nfoo\n"); + // List item with hr -> hr needs to not use the same as the marker + assertRoundTrip("* ___\n"); + assertRoundTrip("- ___\n"); } @Test From a88ab234e01ab4e1f39e1466490536f7d1f5d93b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 10 Feb 2024 11:54:30 +1100 Subject: [PATCH 049/345] README: Bump versions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bcf552c5b..5e1342740 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.20.0 + 0.21.0 ``` @@ -234,7 +234,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.20.0 + 0.21.0 ``` From 2cfe732fc8c67ab04350c1b54326e46ce4039d60 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 2 Oct 2023 19:26:48 +1100 Subject: [PATCH 050/345] Test on Java 21 Bump `actions/setup-java` to `v2` as well, which requires specifying a distribution and use 8 instead of 1.8. --- .github/workflows/ci.yml | 15 +++++++++------ .github/workflows/release.yml | 5 +++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 381d7c73f..df0fa28fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,15 +9,16 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [1.8, 11, 17] + java: [8, 11, 17, 21] steps: - name: Checkout sources uses: actions/checkout@v2 - name: Set up JDK - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: ${{ matrix.java }} + distribution: 'zulu' - name: Build run: mvn -B package javadoc:javadoc @@ -29,9 +30,10 @@ jobs: uses: actions/checkout@v2 - name: Set up JDK - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: - java-version: 1.8 + java-version: 8 + distribution: 'zulu' - name: Build with coverage run: mvn -B -Pcoverage clean test jacoco:report-aggregate @@ -46,9 +48,10 @@ jobs: uses: actions/checkout@v2 - name: Set up JDK - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: - java-version: 1.8 + java-version: 8 + distribution: 'zulu' - name: Android Lint checks run: cd commonmark-android-test && ./gradlew :app:lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4edf451c0..2d269e640 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,9 +17,10 @@ jobs: uses: actions/checkout@v2 - name: Set up Maven Central repository - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: - java-version: 1.8 + java-version: 8 + distribution: 'zulu' server-id: ossrh server-username: MAVEN_USERNAME # env variable to use for username in release server-password: MAVEN_PASSWORD # env variable to use for password in release From 5311b9bd6d09441946a736c06b2a298c2a916368 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 10 Feb 2024 11:56:38 +1100 Subject: [PATCH 051/345] Raise minimum version to Java 11 --- .github/workflows/ci.yml | 6 +++--- README.md | 6 +++--- pom.xml | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df0fa28fb..3fa93f210 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [8, 11, 17, 21] + java: [11, 17, 21] steps: - name: Checkout sources uses: actions/checkout@v2 @@ -32,7 +32,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v2 with: - java-version: 8 + java-version: 11 distribution: 'zulu' - name: Build with coverage @@ -50,7 +50,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v2 with: - java-version: 8 + java-version: 11 distribution: 'zulu' - name: Android Lint checks diff --git a/README.md b/README.md index 5e1342740..dc27c0987 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ full library with a nice API and the following features: * Flexible (manipulate the AST after parsing, customize HTML rendering) * Extensible (tables, strikethrough, autolinking and more, see below) -The library is supported on Java 8 or later. It should work on Java 7 -and Android too, but that is on a best-effort basis, please report -problems. For Android the minimum API level is 19, see the +The library is supported on Java 11 and later. It should work on Android too, +but that is on a best-effort basis, please report problems. For Android the +minimum API level is 19, see the [commonmark-android-test](commonmark-android-test) directory. Coordinates for core library (see all on [Maven Central]): diff --git a/pom.xml b/pom.xml index f5c7749ef..da06b2c2b 100644 --- a/pom.xml +++ b/pom.xml @@ -38,10 +38,9 @@ org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + 3.12.1 - 7 - 7 + 11 From 01fd7732e55b3aaa98a18c2d54b2d5dbc5dac4fa Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 10 Feb 2024 12:38:49 +1100 Subject: [PATCH 052/345] Bump release workflow version as well --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2d269e640..2ecfa6d49 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Maven Central repository uses: actions/setup-java@v2 with: - java-version: 8 + java-version: 11 distribution: 'zulu' server-id: ossrh server-username: MAVEN_USERNAME # env variable to use for username in release From 979d2f6bbbc6ccaa174946e9dc65d539a37c1f1c Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 15 Feb 2024 18:19:04 +1100 Subject: [PATCH 053/345] Documentation --- README.md | 35 ++++++++++++++----- commonmark/pom.xml | 2 +- .../java/org/commonmark/package-info.java | 1 + .../markdown/MarkdownNodeRendererContext.java | 3 ++ .../markdown/MarkdownNodeRendererFactory.java | 2 +- .../renderer/markdown/MarkdownRenderer.java | 17 ++++++--- .../renderer/markdown/package-info.java | 4 +++ .../renderer/text/package-info.java | 2 +- .../org/commonmark/test/UsageExampleTest.java | 17 +++++++-- 9 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/renderer/markdown/package-info.java diff --git a/README.md b/README.md index bcf552c5b..25df264ef 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,14 @@ Java library for parsing and rendering [Markdown] text according to the Introduction ------------ -Provides classes for parsing input to an abstract syntax tree of nodes -(AST), visiting and manipulating nodes, and rendering to HTML. It -started out as a port of [commonmark.js], but has since evolved into a -full library with a nice API and the following features: +Provides classes for parsing input to an abstract syntax tree (AST), +visiting and manipulating nodes, and rendering to HTML or back to Markdown. +It started out as a port of [commonmark.js], but has since evolved into an +extensible library with the following features: * Small (core has no dependencies, extensions in separate artifacts) -* Fast (10-20 times faster than pegdown, see benchmarks in repo) +* Fast (10-20 times faster than [pegdown] which used to be a popular Markdown + library, see benchmarks in repo) * Flexible (manipulate the AST after parsing, customize HTML rendering) * Extensible (tables, strikethrough, autolinking and more, see below) @@ -63,9 +64,9 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; Parser parser = Parser.builder().build(); -Node document = parser.parse("This is *Sparta*"); +Node document = parser.parse("This is *Markdown*"); HtmlRenderer renderer = HtmlRenderer.builder().build(); -renderer.render(document); // "

This is Sparta

\n" +renderer.render(document); // "

This is Markdown

\n" ``` This uses the parser and renderer with default options. Both builders have @@ -81,8 +82,23 @@ to which tags are allowed, etc. That is the responsibility of the caller, and if you expose the resulting HTML, you probably want to run a sanitizer on it after this. -For rendering to plain text, there's also a `TextContentRenderer` with -a very similar API. +#### Render to Markdown + +```java +import org.commonmark.node.*; +import org.commonmark.renderer.markdown.MarkdownRenderer; + +MarkdownRenderer renderer = MarkdownRenderer.builder().build(); +Node document = new Document(); +Heading heading = new Heading(); +heading.setLevel(2); +heading.appendChild(new Text("My title")); +document.appendChild(heading); + +renderer.render(document); // "## My title\n" +``` + +For rendering to plain text with minimal markup, there's also `TextContentRenderer`. #### Use a visitor to process parsed nodes @@ -390,6 +406,7 @@ BSD (2-clause) licensed, see LICENSE.txt file. [CommonMark]: https://commonmark.org/ [Markdown]: https://daringfireball.net/projects/markdown/ [commonmark.js]: https://github.com/commonmark/commonmark.js +[pegdown]: https://github.com/sirthias/pegdown [CommonMark Dingus]: https://spec.commonmark.org/dingus/ [Maven Central]: https://search.maven.org/#search|ga|1|g%3A%22org.commonmark%22 [Semantic Versioning]: https://semver.org/ diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 64c456192..9988370a8 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -9,7 +9,7 @@ commonmark commonmark-java core - Core of commonmark-java (implementation of CommonMark for parsing markdown and rendering to HTML) + Core of commonmark-java (a library for parsing Markdown to an AST, modifying the AST and rendering it to HTML or Markdown) diff --git a/commonmark/src/main/java/org/commonmark/package-info.java b/commonmark/src/main/java/org/commonmark/package-info.java index e784703e9..b683017f6 100644 --- a/commonmark/src/main/java/org/commonmark/package-info.java +++ b/commonmark/src/main/java/org/commonmark/package-info.java @@ -4,6 +4,7 @@ *
  • {@link org.commonmark.parser} for parsing input text to AST nodes
  • *
  • {@link org.commonmark.node} for AST node types and visitors
  • *
  • {@link org.commonmark.renderer.html} for HTML rendering
  • + *
  • {@link org.commonmark.renderer.markdown} for Markdown rendering
  • * */ package org.commonmark; diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java index 5805c458b..40640d1b4 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java @@ -4,6 +4,9 @@ import java.util.Set; +/** + * Context that is passed to custom node renderers, see {@link MarkdownNodeRendererFactory#create}. + */ public interface MarkdownNodeRendererContext { /** diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java index adfe8a07b..14221ea56 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java @@ -5,7 +5,7 @@ import java.util.Set; /** - * Factory for instantiating new node renderers ƒor rendering. + * Factory for instantiating new node renderers for rendering custom nodes. */ public interface MarkdownNodeRendererFactory { diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java index 926105202..25fa9a142 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java @@ -9,14 +9,17 @@ import java.util.*; /** - * Renders nodes to CommonMark Markdown. + * Renders nodes to Markdown (CommonMark syntax); use {@link #builder()} to create a renderer. *

    - * Note that it does not currently attempt to preserve the exact syntax of the original input Markdown (if any): + * Note that it doesn't currently preserve the exact syntax of the original input Markdown (if any): *

      *
    • Headings are output as ATX headings if possible (multi-line headings need Setext headings)
    • *
    • Escaping might be over-eager, e.g. a plain {@code *} might be escaped * even though it doesn't need to be in that particular context
    • + *
    • Leading whitespace in paragraphs is not preserved
    • *
    + * However, it should produce Markdown that is semantically equivalent to the input, i.e. if the Markdown was parsed + * again and compared against the original AST, it should be the same (minus bugs). */ public class MarkdownRenderer implements Renderer { @@ -66,7 +69,7 @@ public String render(Node node) { */ public static class Builder { - private List nodeRendererFactories = new ArrayList<>(); + private final List nodeRendererFactories = new ArrayList<>(); /** * @return the configured {@link MarkdownRenderer} @@ -106,9 +109,15 @@ public Builder extensions(Iterable extensions) { } /** - * Extension for {@link MarkdownRenderer}. + * Extension for {@link MarkdownRenderer} for rendering custom nodes. */ public interface MarkdownRendererExtension extends Extension { + + /** + * Extend Markdown rendering, usually by registering custom node renderers using {@link Builder#nodeRendererFactory}. + * + * @param rendererBuilder the renderer builder to extend + */ void extend(Builder rendererBuilder); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/package-info.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/package-info.java new file mode 100644 index 000000000..f707671d5 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/package-info.java @@ -0,0 +1,4 @@ +/** + * Markdown rendering (see {@link org.commonmark.renderer.markdown.MarkdownRenderer}) + */ +package org.commonmark.renderer.markdown; diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/package-info.java b/commonmark/src/main/java/org/commonmark/renderer/text/package-info.java index 07a558091..8309f4bd6 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/package-info.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/package-info.java @@ -1,4 +1,4 @@ /** - * Text content rendering (see {@link org.commonmark.renderer.text.TextContentRenderer}) + * Plain text rendering with minimal markup (see {@link org.commonmark.renderer.text.TextContentRenderer}) */ package org.commonmark.renderer.text; diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 9ff646630..08235965a 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -4,6 +4,7 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.*; +import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.Ignore; import org.junit.Test; @@ -22,9 +23,21 @@ public class UsageExampleTest { @Test public void parseAndRender() { Parser parser = Parser.builder().build(); - Node document = parser.parse("This is *Sparta*"); + Node document = parser.parse("This is *Markdown*"); HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build(); - assertEquals("

    This is Sparta

    \n", renderer.render(document)); + assertEquals("

    This is Markdown

    \n", renderer.render(document)); + } + + @Test + public void renderToMarkdown() { + MarkdownRenderer renderer = MarkdownRenderer.builder().build(); + Node document = new Document(); + Heading heading = new Heading(); + heading.setLevel(2); + heading.appendChild(new Text("My title")); + document.appendChild(heading); + + assertEquals("## My title\n", renderer.render(document)); } @Test From 94eb5ff58a9cf589d28945e79a8a702abcbf6344 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 24 Feb 2024 11:23:27 +1100 Subject: [PATCH 054/345] Add test for hard line break in heading --- .../org/commonmark/renderer/markdown/MarkdownRendererTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index eaabe837c..9bab92bcc 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -30,6 +30,7 @@ public void testHeadings() { assertRoundTrip("###### foo\n"); assertRoundTrip("Foo\nbar\n===\n"); + assertRoundTrip("Foo \nbar\n===\n"); assertRoundTrip("[foo\nbar](/url)\n===\n"); assertRoundTrip("# foo\n\nbar\n"); From ce9e056352ee9d8c1a415a0b4354e34209c51ba6 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 24 Feb 2024 14:36:34 +1100 Subject: [PATCH 055/345] Modular JAR: Add module-info module descriptor We've waited long enough for the ecosystem and tools such as Android to catch up. Making it proper modular means people can run jlink on it. Fixes #125. --- CHANGELOG.md | 9 + README.md | 13 +- commonmark-ext-autolink/pom.xml | 18 +- .../src/main/java/module-info.java | 6 + commonmark-ext-gfm-strikethrough/pom.xml | 16 -- .../src/main/java/module-info.java | 5 + commonmark-ext-gfm-tables/pom.xml | 16 -- .../src/main/java/module-info.java | 5 + .../gfm/tables/internal/TableBlockParser.java | 16 +- .../internal/TableMarkdownNodeRenderer.java | 5 +- commonmark-ext-heading-anchor/pom.xml | 16 -- .../src/main/java/module-info.java | 5 + commonmark-ext-image-attributes/pom.xml | 16 -- .../src/main/java/module-info.java | 5 + commonmark-ext-ins/pom.xml | 16 -- .../src/main/java/module-info.java | 5 + commonmark-ext-task-list-items/pom.xml | 16 -- .../src/main/java/module-info.java | 5 + commonmark-ext-yaml-front-matter/pom.xml | 16 -- .../src/main/java/module-info.java | 5 + .../internal/YamlFrontMatterBlockParser.java | 4 +- commonmark-test-util/pom.xml | 16 -- .../src/main/java/module-info.java | 6 + commonmark/pom.xml | 16 -- commonmark/src/main/java/module-info.java | 13 + .../commonmark/internal/BlockQuoteParser.java | 5 +- .../java/org/commonmark/internal/Bracket.java | 2 +- .../commonmark/internal/DocumentParser.java | 36 ++- .../internal/FencedCodeBlockParser.java | 7 +- .../commonmark/internal/HeadingParser.java | 9 +- .../commonmark/internal/HtmlBlockParser.java | 18 +- .../internal/IndentedCodeBlockParser.java | 3 +- .../commonmark/internal/InlineParserImpl.java | 17 +- .../LinkReferenceDefinitionParser.java | 4 +- .../internal/inline/AutolinkInlineParser.java | 2 + .../inline/BackslashInlineParser.java | 2 +- .../inline/BackticksInlineParser.java | 6 +- .../internal/inline/EntityInlineParser.java | 4 +- .../internal/inline/HtmlInlineParser.java | 4 +- .../internal/inline/InlineParserState.java | 3 + .../internal/inline/ParsedInline.java | 1 + .../internal/inline/ParsedInlineImpl.java | 1 + .../commonmark/internal/util/CharMatcher.java | 6 - .../commonmark/internal/util/LinkScanner.java | 49 +++- .../org/commonmark/internal/util/Parsing.java | 236 ------------------ .../inline => parser/beta}/Position.java | 2 +- .../inline => parser/beta}/Scanner.java | 4 +- .../markdown/CoreMarkdownNodeRenderer.java | 12 +- .../renderer/markdown/MarkdownWriter.java | 2 +- .../{internal/util => text}/AsciiMatcher.java | 5 +- .../java/org/commonmark/text/CharMatcher.java | 13 + .../java/org/commonmark/text/Characters.java | 159 ++++++++++++ .../inline => parser/beta}/ScannerTest.java | 4 +- .../CharactersTest.java} | 6 +- pom.xml | 2 +- 55 files changed, 420 insertions(+), 473 deletions(-) create mode 100644 commonmark-ext-autolink/src/main/java/module-info.java create mode 100644 commonmark-ext-gfm-strikethrough/src/main/java/module-info.java create mode 100644 commonmark-ext-gfm-tables/src/main/java/module-info.java create mode 100644 commonmark-ext-heading-anchor/src/main/java/module-info.java create mode 100644 commonmark-ext-image-attributes/src/main/java/module-info.java create mode 100644 commonmark-ext-ins/src/main/java/module-info.java create mode 100644 commonmark-ext-task-list-items/src/main/java/module-info.java create mode 100644 commonmark-ext-yaml-front-matter/src/main/java/module-info.java create mode 100644 commonmark-test-util/src/main/java/module-info.java create mode 100644 commonmark/src/main/java/module-info.java delete mode 100644 commonmark/src/main/java/org/commonmark/internal/util/CharMatcher.java rename commonmark/src/main/java/org/commonmark/{internal/inline => parser/beta}/Position.java (89%) rename commonmark/src/main/java/org/commonmark/{internal/inline => parser/beta}/Scanner.java (99%) rename commonmark/src/main/java/org/commonmark/{internal/util => text}/AsciiMatcher.java (94%) create mode 100644 commonmark/src/main/java/org/commonmark/text/CharMatcher.java create mode 100644 commonmark/src/main/java/org/commonmark/text/Characters.java rename commonmark/src/test/java/org/commonmark/{internal/inline => parser/beta}/ScannerTest.java (97%) rename commonmark/src/test/java/org/commonmark/{internal/util/ParsingTest.java => text/CharactersTest.java} (78%) diff --git a/CHANGELOG.md b/CHANGELOG.md index f558a3601..157c166b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## Unreleased +### Changed +- Modular JAR: Require at least Java 11 and add a module descriptor (module-info), + remove no longer necessary `Automatic-Module-Name` header +- New package `org.commonmark.parser.beta` containing classes that are not part of + the stable API but are exported from the module (because they might be useful for + extension parsers). + ## [0.21.0] - 2022-11-17 ### Added - GitHub strikethrough: With the previous version we adjusted the @@ -379,6 +387,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.22.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.21.0...commonmark-parent-0.22.0 [0.21.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.20.0...commonmark-parent-0.21.0 [0.20.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.19.0...commonmark-parent-0.20.0 [0.19.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.2...commonmark-parent-0.19.0 diff --git a/README.md b/README.md index da7a07dd2..5cce587d9 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,11 @@ extensible library with the following features: * Flexible (manipulate the AST after parsing, customize HTML rendering) * Extensible (tables, strikethrough, autolinking and more, see below) -The library is supported on Java 11 and later. It should work on Android too, -but that is on a best-effort basis, please report problems. For Android the -minimum API level is 19, see the +The library is supported on Java 11 and later. It works on Android too, but +that is currently not part of the builds and so only supported on a best-effort +basis, please report any problems. For Android the minimum API level is 19, see +the [commonmark-android-test](commonmark-android-test) +directory. [commonmark-android-test](commonmark-android-test) directory. Coordinates for core library (see all on [Maven Central]): @@ -44,7 +46,8 @@ The module names to use in Java 9 are `org.commonmark`, Note that for 0.x releases of this library, the API is not considered stable yet and may break between minor releases. After 1.0, [Semantic Versioning] will -be followed. +be followed. A package containing `beta` means it's not subject to stable API +guarantees yet; but for normal usage it should not be necessary to use. See the [spec.txt](commonmark-test-util/src/main/resources/spec.txt) file if you're wondering which version of the spec is currently @@ -399,7 +402,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) file. License ------- -Copyright (c) 2015-2019 Atlassian and others. +Copyright (c) Atlassian and others. BSD (2-clause) licensed, see LICENSE.txt file. diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index c82ecdae8..2f70f7a0e 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -12,7 +12,7 @@ commonmark-java extension for turning plain URLs and email addresses into links - 0.10.0 + 0.11.0 @@ -33,20 +33,4 @@
    - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.commonmark.ext.autolink - - - - - - - diff --git a/commonmark-ext-autolink/src/main/java/module-info.java b/commonmark-ext-autolink/src/main/java/module-info.java new file mode 100644 index 000000000..6fe98c3b1 --- /dev/null +++ b/commonmark-ext-autolink/src/main/java/module-info.java @@ -0,0 +1,6 @@ +module org.commonmark.ext.autolink { + exports org.commonmark.ext.autolink; + + requires org.commonmark; + requires org.nibor.autolink; +} diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 878a1e586..b452073ab 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -24,20 +24,4 @@ - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.commonmark.ext.gfm.strikethrough - - - - - - - diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/module-info.java b/commonmark-ext-gfm-strikethrough/src/main/java/module-info.java new file mode 100644 index 000000000..772710f00 --- /dev/null +++ b/commonmark-ext-gfm-strikethrough/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module org.commonmark.ext.gfm.strikethrough { + exports org.commonmark.ext.gfm.strikethrough; + + requires org.commonmark; +} diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index c6448889f..01fe2b23c 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -24,20 +24,4 @@ - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.commonmark.ext.gfm.tables - - - - - - - diff --git a/commonmark-ext-gfm-tables/src/main/java/module-info.java b/commonmark-ext-gfm-tables/src/main/java/module-info.java new file mode 100644 index 000000000..472c84c3d --- /dev/null +++ b/commonmark-ext-gfm-tables/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module org.commonmark.ext.gfm.tables { + exports org.commonmark.ext.gfm.tables; + + requires org.commonmark; +} diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index b7cea14db..2518a3eee 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java @@ -1,7 +1,6 @@ package org.commonmark.ext.gfm.tables.internal; import org.commonmark.ext.gfm.tables.*; -import org.commonmark.internal.util.Parsing; import org.commonmark.node.Block; import org.commonmark.node.Node; import org.commonmark.node.SourceSpan; @@ -9,6 +8,7 @@ import org.commonmark.parser.SourceLine; import org.commonmark.parser.SourceLines; import org.commonmark.parser.block.*; +import org.commonmark.text.Characters; import java.util.ArrayList; import java.util.List; @@ -39,11 +39,11 @@ public Block getBlock() { @Override public BlockContinue tryContinue(ParserState state) { CharSequence content = state.getLine().getContent(); - int pipe = Parsing.find('|', content, state.getNextNonSpaceIndex()); + int pipe = Characters.find('|', content, state.getNextNonSpaceIndex()); if (pipe != -1) { if (pipe == state.getNextNonSpaceIndex()) { // If we *only* have a pipe character (and whitespace), that is not a valid table row and ends the table. - if (Parsing.skipSpaceTab(content, pipe + 1, content.length()) == content.length()) { + if (Characters.skipSpaceTab(content, pipe + 1, content.length()) == content.length()) { // We also don't want the pipe to be added via lazy continuation. canHaveLazyContinuationLines = false; return BlockContinue.none(); @@ -124,8 +124,8 @@ private TableCell parseCell(SourceLine cell, int column, InlineParser inlinePars } CharSequence content = cell.getContent(); - int start = Parsing.skipSpaceTab(content, 0, content.length()); - int end = Parsing.skipSpaceTabBackwards(content, content.length() - 1, start); + int start = Characters.skipSpaceTab(content, 0, content.length()); + int end = Characters.skipSpaceTabBackwards(content, content.length() - 1, start); inlineParser.parse(SourceLines.of(cell.substring(start, end + 1)), tableCell); return tableCell; @@ -133,14 +133,14 @@ private TableCell parseCell(SourceLine cell, int column, InlineParser inlinePars private static List split(SourceLine line) { CharSequence row = line.getContent(); - int nonSpace = Parsing.skipSpaceTab(row, 0, row.length()); + int nonSpace = Characters.skipSpaceTab(row, 0, row.length()); int cellStart = nonSpace; int cellEnd = row.length(); if (row.charAt(nonSpace) == '|') { // This row has leading/trailing pipes - skip the leading pipe cellStart = nonSpace + 1; // Strip whitespace from the end but not the pipe or we could miss an empty ("||") cell - int nonSpaceEnd = Parsing.skipSpaceTabBackwards(row, row.length() - 1, cellStart); + int nonSpaceEnd = Characters.skipSpaceTabBackwards(row, row.length() - 1, cellStart); cellEnd = nonSpaceEnd + 1; } List cells = new ArrayList<>(); @@ -267,7 +267,7 @@ public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { List paragraphLines = matchedBlockParser.getParagraphLines().getLines(); - if (paragraphLines.size() == 1 && Parsing.find('|', paragraphLines.get(0).getContent(), 0) != -1) { + if (paragraphLines.size() == 1 && Characters.find('|', paragraphLines.get(0).getContent(), 0) != -1) { SourceLine line = state.getLine(); SourceLine separatorLine = line.substring(state.getIndex(), line.getContent().length()); List columns = parseSeparator(separatorLine.getContent()); diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java index 487147164..2fe60f80d 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java @@ -1,12 +1,11 @@ package org.commonmark.ext.gfm.tables.internal; import org.commonmark.ext.gfm.tables.*; -import org.commonmark.internal.util.AsciiMatcher; -import org.commonmark.internal.util.CharMatcher; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; import org.commonmark.renderer.markdown.MarkdownWriter; +import org.commonmark.text.AsciiMatcher; import java.util.ArrayList; import java.util.List; @@ -18,7 +17,7 @@ public class TableMarkdownNodeRenderer extends TableNodeRenderer implements Node private final MarkdownWriter writer; private final MarkdownNodeRendererContext context; - private final CharMatcher pipe = AsciiMatcher.builder().c('|').build(); + private final AsciiMatcher pipe = AsciiMatcher.builder().c('|').build(); private final List columns = new ArrayList<>(); diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 49bc4a032..330490ea6 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -24,20 +24,4 @@ - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.commonmark.ext.heading.anchor - - - - - - - diff --git a/commonmark-ext-heading-anchor/src/main/java/module-info.java b/commonmark-ext-heading-anchor/src/main/java/module-info.java new file mode 100644 index 000000000..3b94c75ec --- /dev/null +++ b/commonmark-ext-heading-anchor/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module org.commonmark.ext.heading.anchor { + exports org.commonmark.ext.heading.anchor; + + requires org.commonmark; +} diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 959b1406c..6fbec9007 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -24,20 +24,4 @@ - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.commonmark.ext.image.attributes - - - - - - - diff --git a/commonmark-ext-image-attributes/src/main/java/module-info.java b/commonmark-ext-image-attributes/src/main/java/module-info.java new file mode 100644 index 000000000..171281091 --- /dev/null +++ b/commonmark-ext-image-attributes/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module org.commonmark.ext.image.attributes { + exports org.commonmark.ext.image.attributes; + + requires org.commonmark; +} diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 708532472..68e5f627b 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -24,20 +24,4 @@ - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.commonmark.ext.ins - - - - - - - diff --git a/commonmark-ext-ins/src/main/java/module-info.java b/commonmark-ext-ins/src/main/java/module-info.java new file mode 100644 index 000000000..aa5c5e84c --- /dev/null +++ b/commonmark-ext-ins/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module org.commonmark.ext.ins { + exports org.commonmark.ext.ins; + + requires org.commonmark; +} diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index ec3ac1a1f..3e3bebf32 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -24,20 +24,4 @@ - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.commonmark.ext.task.list.items - - - - - - - diff --git a/commonmark-ext-task-list-items/src/main/java/module-info.java b/commonmark-ext-task-list-items/src/main/java/module-info.java new file mode 100644 index 000000000..30134c51b --- /dev/null +++ b/commonmark-ext-task-list-items/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module org.commonmark.ext.task.list.items { + exports org.commonmark.ext.task.list.items; + + requires org.commonmark; +} diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index e5e120caa..97bdc5c48 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -24,20 +24,4 @@ - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.commonmark.ext.front.matter - - - - - - - diff --git a/commonmark-ext-yaml-front-matter/src/main/java/module-info.java b/commonmark-ext-yaml-front-matter/src/main/java/module-info.java new file mode 100644 index 000000000..20d38fe0a --- /dev/null +++ b/commonmark-ext-yaml-front-matter/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module org.commonmark.ext.front.matter { + exports org.commonmark.ext.front.matter; + + requires org.commonmark; +} diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java index 2010b4f71..469cf4e2f 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java @@ -2,8 +2,8 @@ import org.commonmark.ext.front.matter.YamlFrontMatterBlock; import org.commonmark.ext.front.matter.YamlFrontMatterNode; -import org.commonmark.internal.DocumentBlockParser; import org.commonmark.node.Block; +import org.commonmark.node.Document; import org.commonmark.parser.InlineParser; import org.commonmark.parser.SourceLine; import org.commonmark.parser.block.*; @@ -119,7 +119,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar CharSequence line = state.getLine().getContent(); BlockParser parentParser = matchedBlockParser.getMatchedBlockParser(); // check whether this line is the first line of whole document or not - if (parentParser instanceof DocumentBlockParser && parentParser.getBlock().getFirstChild() == null && + if (parentParser.getBlock() instanceof Document && parentParser.getBlock().getFirstChild() == null && REGEX_BEGIN.matcher(line).matches()) { return BlockStart.of(new YamlFrontMatterBlockParser()).atIndex(state.getNextNonSpaceIndex()); } diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index c61000412..ce332e6c2 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -18,20 +18,4 @@ - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.commonmark.testutil - - - - - - - diff --git a/commonmark-test-util/src/main/java/module-info.java b/commonmark-test-util/src/main/java/module-info.java new file mode 100644 index 000000000..ef983a513 --- /dev/null +++ b/commonmark-test-util/src/main/java/module-info.java @@ -0,0 +1,6 @@ +module org.commonmark.testutil { + exports org.commonmark.testutil; + exports org.commonmark.testutil.example; + + requires junit; +} diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 9988370a8..524ba9c42 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -29,22 +29,6 @@ - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.commonmark - - - - - - - benchmark diff --git a/commonmark/src/main/java/module-info.java b/commonmark/src/main/java/module-info.java new file mode 100644 index 000000000..009fc7d18 --- /dev/null +++ b/commonmark/src/main/java/module-info.java @@ -0,0 +1,13 @@ +module org.commonmark { + exports org.commonmark; + exports org.commonmark.node; + exports org.commonmark.parser; + exports org.commonmark.parser.beta; + exports org.commonmark.parser.block; + exports org.commonmark.parser.delimiter; + exports org.commonmark.renderer; + exports org.commonmark.renderer.html; + exports org.commonmark.renderer.markdown; + exports org.commonmark.renderer.text; + exports org.commonmark.text; +} diff --git a/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java b/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java index 00cdbc11e..87c923e06 100644 --- a/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java @@ -4,6 +4,7 @@ import org.commonmark.node.Block; import org.commonmark.node.BlockQuote; import org.commonmark.parser.block.*; +import org.commonmark.text.Characters; public class BlockQuoteParser extends AbstractBlockParser { @@ -30,7 +31,7 @@ public BlockContinue tryContinue(ParserState state) { if (isMarker(state, nextNonSpace)) { int newColumn = state.getColumn() + state.getIndent() + 1; // optional following space or tab - if (Parsing.isSpaceOrTab(state.getLine().getContent(), nextNonSpace + 1)) { + if (Characters.isSpaceOrTab(state.getLine().getContent(), nextNonSpace + 1)) { newColumn++; } return BlockContinue.atColumn(newColumn); @@ -50,7 +51,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar if (isMarker(state, nextNonSpace)) { int newColumn = state.getColumn() + state.getIndent() + 1; // optional following space or tab - if (Parsing.isSpaceOrTab(state.getLine().getContent(), nextNonSpace + 1)) { + if (Characters.isSpaceOrTab(state.getLine().getContent(), nextNonSpace + 1)) { newColumn++; } return BlockStart.of(new BlockQuoteParser()).atColumn(newColumn); diff --git a/commonmark/src/main/java/org/commonmark/internal/Bracket.java b/commonmark/src/main/java/org/commonmark/internal/Bracket.java index 46296262f..9c73a1327 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Bracket.java +++ b/commonmark/src/main/java/org/commonmark/internal/Bracket.java @@ -1,7 +1,7 @@ package org.commonmark.internal; -import org.commonmark.internal.inline.Position; import org.commonmark.node.Text; +import org.commonmark.parser.beta.Position; /** * Opening bracket for links ([) or images (![). diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 086c3dbc0..2cc37e306 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -5,6 +5,7 @@ import org.commonmark.parser.*; import org.commonmark.parser.block.*; import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.text.Characters; import java.io.BufferedReader; import java.io.IOException; @@ -112,7 +113,7 @@ public static void checkEnabledBlockTypes(Set> enabledBlo public Document parse(String input) { int lineStart = 0; int lineBreak; - while ((lineBreak = Parsing.findLineBreak(input, lineStart)) != -1) { + while ((lineBreak = Characters.findLineBreak(input, lineStart)) != -1) { String line = input.substring(lineStart, lineBreak); parseLine(line); if (lineBreak + 1 < input.length() && input.charAt(lineBreak) == '\r' && input.charAt(lineBreak + 1) == '\n') { @@ -230,7 +231,7 @@ private void parseLine(CharSequence ln) { findNextNonSpace(); // this is a little performance optimization: - if (isBlank() || (indent < Parsing.CODE_BLOCK_INDENT && Parsing.isLetter(this.line.getContent(), nextNonSpace))) { + if (isBlank() || (indent < Parsing.CODE_BLOCK_INDENT && Characters.isLetter(this.line.getContent(), nextNonSpace))) { setNewIndex(nextNonSpace); break; } @@ -315,7 +316,7 @@ private void setLine(CharSequence ln) { column = 0; columnIsInTab = false; - CharSequence lineContent = Parsing.prepareLine(ln); + CharSequence lineContent = prepareLine(ln); SourceSpan sourceSpan = null; if (includeSourceSpans != IncludeSourceSpans.NONE) { sourceSpan = SourceSpan.of(lineIndex, 0, lineContent.length()); @@ -542,6 +543,35 @@ private void closeBlockParsers(int count) { } } + /** + * Prepares the input line replacing {@code \0} + */ + private static CharSequence prepareLine(CharSequence line) { + // Avoid building a new string in the majority of cases (no \0) + StringBuilder sb = null; + int length = line.length(); + for (int i = 0; i < length; i++) { + char c = line.charAt(i); + if (c == '\0') { + if (sb == null) { + sb = new StringBuilder(length); + sb.append(line, 0, i); + } + sb.append('\uFFFD'); + } else { + if (sb != null) { + sb.append(c); + } + } + } + + if (sb != null) { + return sb.toString(); + } else { + return line; + } + } + private static class MatchedBlockParserImpl implements MatchedBlockParser { private final BlockParser matchedBlockParser; diff --git a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java index 2d7d2c0c9..a16758dd4 100644 --- a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java @@ -5,6 +5,7 @@ import org.commonmark.node.FencedCodeBlock; import org.commonmark.parser.SourceLine; import org.commonmark.parser.block.*; +import org.commonmark.text.Characters; import static org.commonmark.internal.util.Escaping.unescapeString; @@ -103,7 +104,7 @@ private static FencedCodeBlockParser checkOpener(CharSequence line, int index, i } if (backticks >= 3 && tildes == 0) { // spec: If the info string comes after a backtick fence, it may not contain any backtick characters. - if (Parsing.find('`', line, index + backticks) != -1) { + if (Characters.find('`', line, index + backticks) != -1) { return null; } return new FencedCodeBlockParser('`', backticks, indent); @@ -121,12 +122,12 @@ private static FencedCodeBlockParser checkOpener(CharSequence line, int index, i private boolean isClosing(CharSequence line, int index) { char fenceChar = block.getFenceChar(); int fenceLength = block.getFenceLength(); - int fences = Parsing.skip(fenceChar, line, index, line.length()) - index; + int fences = Characters.skip(fenceChar, line, index, line.length()) - index; if (fences < fenceLength) { return false; } // spec: The closing code fence [...] may be followed only by spaces, which are ignored. - int after = Parsing.skipSpaceTab(line, index + fences, line.length()); + int after = Characters.skipSpaceTab(line, index + fences, line.length()); return after == line.length(); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index 81c60d0d1..d422c1241 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -1,14 +1,15 @@ package org.commonmark.internal; -import org.commonmark.internal.inline.Position; -import org.commonmark.internal.inline.Scanner; import org.commonmark.internal.util.Parsing; import org.commonmark.node.Block; import org.commonmark.node.Heading; import org.commonmark.parser.InlineParser; import org.commonmark.parser.SourceLine; import org.commonmark.parser.SourceLines; +import org.commonmark.parser.beta.Position; +import org.commonmark.parser.beta.Scanner; import org.commonmark.parser.block.*; +import org.commonmark.text.Characters; public class HeadingParser extends AbstractBlockParser { @@ -148,8 +149,8 @@ private static int getSetextHeadingLevel(CharSequence line, int index) { } private static boolean isSetextHeadingRest(CharSequence line, int index, char marker) { - int afterMarker = Parsing.skip(marker, line, index, line.length()); - int afterSpace = Parsing.skipSpaceTab(line, afterMarker, line.length()); + int afterMarker = Characters.skip(marker, line, index, line.length()); + int afterSpace = Characters.skipSpaceTab(line, afterMarker, line.length()); return afterSpace >= line.length(); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java index 0e2050567..ce66c20da 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java @@ -1,6 +1,5 @@ package org.commonmark.internal; -import org.commonmark.internal.util.Parsing; import org.commonmark.node.Block; import org.commonmark.node.HtmlBlock; import org.commonmark.node.Paragraph; @@ -11,6 +10,21 @@ public class HtmlBlockParser extends AbstractBlockParser { + private static final String TAGNAME = "[A-Za-z][A-Za-z0-9-]*"; + private static final String ATTRIBUTENAME = "[a-zA-Z_:][a-zA-Z0-9:._-]*"; + private static final String UNQUOTEDVALUE = "[^\"'=<>`\\x00-\\x20]+"; + private static final String SINGLEQUOTEDVALUE = "'[^']*'"; + private static final String DOUBLEQUOTEDVALUE = "\"[^\"]*\""; + private static final String ATTRIBUTEVALUE = "(?:" + UNQUOTEDVALUE + "|" + SINGLEQUOTEDVALUE + + "|" + DOUBLEQUOTEDVALUE + ")"; + private static final String ATTRIBUTEVALUESPEC = "(?:" + "\\s*=" + "\\s*" + ATTRIBUTEVALUE + + ")"; + private static final String ATTRIBUTE = "(?:" + "\\s+" + ATTRIBUTENAME + ATTRIBUTEVALUESPEC + + "?)"; + + private static final String OPENTAG = "<" + TAGNAME + ATTRIBUTE + "*" + "\\s*/?>"; + private static final String CLOSETAG = "]"; + private static final Pattern[][] BLOCK_PATTERNS = new Pattern[][]{ {null, null}, // not used (no type 0) { @@ -54,7 +68,7 @@ public class HtmlBlockParser extends AbstractBlockParser { null // terminated by blank line }, { - Pattern.compile("^(?:" + Parsing.OPENTAG + '|' + Parsing.CLOSETAG + ")\\s*$", Pattern.CASE_INSENSITIVE), + Pattern.compile("^(?:" + OPENTAG + '|' + CLOSETAG + ")\\s*$", Pattern.CASE_INSENSITIVE), null // terminated by blank line } }; diff --git a/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java index af74a587c..3598f5615 100644 --- a/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java @@ -6,6 +6,7 @@ import org.commonmark.node.Paragraph; import org.commonmark.parser.SourceLine; import org.commonmark.parser.block.*; +import org.commonmark.text.Characters; import java.util.ArrayList; import java.util.List; @@ -40,7 +41,7 @@ public void addLine(SourceLine line) { public void closeBlock() { int lastNonBlank = lines.size() - 1; while (lastNonBlank >= 0) { - if (!Parsing.isBlank(lines.get(lastNonBlank))) { + if (!Characters.isBlank(lines.get(lastNonBlank))) { break; } lastNonBlank--; diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index c14b9e885..113e80db9 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -1,15 +1,16 @@ package org.commonmark.internal; -import org.commonmark.internal.inline.Scanner; import org.commonmark.internal.inline.*; import org.commonmark.internal.util.Escaping; import org.commonmark.internal.util.LinkScanner; -import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.SourceLines; +import org.commonmark.parser.beta.Position; +import org.commonmark.parser.beta.Scanner; import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.text.Characters; import java.util.*; @@ -482,12 +483,12 @@ private Node parseText() { if (c == '\n') { // We parsed until the end of the line. Trim any trailing spaces and remember them (for hard line breaks). - int end = Parsing.skipBackwards(' ', content, content.length() - 1, 0) + 1; + int end = Characters.skipBackwards(' ', content, content.length() - 1, 0) + 1; trailingSpaces = content.length() - end; content = content.substring(0, end); } else if (c == Scanner.END) { // For the last line, both tabs and spaces are trimmed for some reason (checked with commonmark.js). - int end = Parsing.skipSpaceTabBackwards(content, content.length() - 1, 0) + 1; + int end = Characters.skipSpaceTabBackwards(content, content.length() - 1, 0) + 1; content = content.substring(0, end); } @@ -525,10 +526,10 @@ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char int after = scanner.peekCodePoint(); // We could be more lazy here, in most cases we don't need to do every match case. - boolean beforeIsPunctuation = before == Scanner.END || Parsing.isPunctuationCodePoint(before); - boolean beforeIsWhitespace = before == Scanner.END || Parsing.isWhitespaceCodePoint(before); - boolean afterIsPunctuation = after == Scanner.END || Parsing.isPunctuationCodePoint(after); - boolean afterIsWhitespace = after == Scanner.END || Parsing.isWhitespaceCodePoint(after); + boolean beforeIsPunctuation = before == Scanner.END || Characters.isPunctuationCodePoint(before); + boolean beforeIsWhitespace = before == Scanner.END || Characters.isWhitespaceCodePoint(before); + boolean afterIsPunctuation = after == Scanner.END || Characters.isPunctuationCodePoint(after); + boolean afterIsWhitespace = after == Scanner.END || Characters.isWhitespaceCodePoint(after); boolean leftFlanking = !afterIsWhitespace && (!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation); diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index 2bceb7549..d11a6f228 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -1,13 +1,13 @@ package org.commonmark.internal; -import org.commonmark.internal.inline.Position; -import org.commonmark.internal.inline.Scanner; import org.commonmark.internal.util.Escaping; import org.commonmark.internal.util.LinkScanner; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.node.SourceSpan; import org.commonmark.parser.SourceLine; import org.commonmark.parser.SourceLines; +import org.commonmark.parser.beta.Position; +import org.commonmark.parser.beta.Scanner; import java.util.ArrayList; import java.util.List; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java index ecfd2d972..36c43e196 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -3,6 +3,8 @@ import org.commonmark.node.Link; import org.commonmark.node.Text; import org.commonmark.parser.SourceLines; +import org.commonmark.parser.beta.Position; +import org.commonmark.parser.beta.Scanner; import java.util.regex.Pattern; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java index f57a67a74..02c136951 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java @@ -2,8 +2,8 @@ import org.commonmark.internal.util.Escaping; import org.commonmark.node.HardLineBreak; -import org.commonmark.node.Node; import org.commonmark.node.Text; +import org.commonmark.parser.beta.Scanner; import java.util.regex.Pattern; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java index ad079444a..bef8e1f99 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -1,9 +1,11 @@ package org.commonmark.internal.inline; -import org.commonmark.internal.util.Parsing; import org.commonmark.node.Code; import org.commonmark.node.Text; import org.commonmark.parser.SourceLines; +import org.commonmark.parser.beta.Position; +import org.commonmark.parser.beta.Scanner; +import org.commonmark.text.Characters; /** * Attempt to parse backticks, returning either a backtick code span or a literal sequence of backticks. @@ -31,7 +33,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { if (content.length() >= 3 && content.charAt(0) == ' ' && content.charAt(content.length() - 1) == ' ' && - Parsing.hasNonSpace(content)) { + Characters.hasNonSpace(content)) { content = content.substring(1, content.length() - 1); } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java index c29b8694f..2b7d296fb 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java @@ -1,8 +1,10 @@ package org.commonmark.internal.inline; -import org.commonmark.internal.util.AsciiMatcher; +import org.commonmark.text.AsciiMatcher; import org.commonmark.internal.util.Html5Entities; import org.commonmark.node.Text; +import org.commonmark.parser.beta.Position; +import org.commonmark.parser.beta.Scanner; /** * Attempts to parse a HTML entity or numeric character reference. diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java index 605901c22..c85ae9d71 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -1,7 +1,9 @@ package org.commonmark.internal.inline; -import org.commonmark.internal.util.AsciiMatcher; +import org.commonmark.text.AsciiMatcher; import org.commonmark.node.HtmlInline; +import org.commonmark.parser.beta.Position; +import org.commonmark.parser.beta.Scanner; /** * Attempt to parse inline HTML. diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java b/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java index f6cb6bf49..ea8689be5 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java @@ -1,5 +1,8 @@ package org.commonmark.internal.inline; +import org.commonmark.parser.beta.Position; +import org.commonmark.parser.beta.Scanner; + public interface InlineParserState { /** diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java index 7e6ece88e..7223c1687 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java @@ -1,6 +1,7 @@ package org.commonmark.internal.inline; import org.commonmark.node.Node; +import org.commonmark.parser.beta.Position; public abstract class ParsedInline { diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java index aea325f27..55f9cc4da 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java @@ -1,6 +1,7 @@ package org.commonmark.internal.inline; import org.commonmark.node.Node; +import org.commonmark.parser.beta.Position; public class ParsedInlineImpl extends ParsedInline { private final Node node; diff --git a/commonmark/src/main/java/org/commonmark/internal/util/CharMatcher.java b/commonmark/src/main/java/org/commonmark/internal/util/CharMatcher.java deleted file mode 100644 index de730e90d..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/util/CharMatcher.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.commonmark.internal.util; - -public interface CharMatcher { - - boolean matches(char c); -} diff --git a/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java b/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java index 3ca34c5f0..ffed047e5 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java @@ -1,6 +1,6 @@ package org.commonmark.internal.util; -import org.commonmark.internal.inline.Scanner; +import org.commonmark.parser.beta.Scanner; public class LinkScanner { @@ -14,7 +14,7 @@ public static boolean scanLinkLabelContent(Scanner scanner) { switch (scanner.peek()) { case '\\': scanner.next(); - if (Parsing.isEscapable(scanner.peek())) { + if (isEscapable(scanner.peek())) { scanner.next(); } break; @@ -44,7 +44,7 @@ public static boolean scanLinkDestination(Scanner scanner) { switch (scanner.peek()) { case '\\': scanner.next(); - if (Parsing.isEscapable(scanner.peek())) { + if (isEscapable(scanner.peek())) { scanner.next(); } break; @@ -100,7 +100,7 @@ public static boolean scanLinkTitleContent(Scanner scanner, char endDelimiter) { char c = scanner.peek(); if (c == '\\') { scanner.next(); - if (Parsing.isEscapable(scanner.peek())) { + if (isEscapable(scanner.peek())) { scanner.next(); } } else if (c == endDelimiter) { @@ -128,7 +128,7 @@ private static boolean scanLinkDestinationWithBalancedParens(Scanner scanner) { return !empty; case '\\': scanner.next(); - if (Parsing.isEscapable(scanner.peek())) { + if (isEscapable(scanner.peek())) { scanner.next(); } break; @@ -160,4 +160,43 @@ private static boolean scanLinkDestinationWithBalancedParens(Scanner scanner) { } return true; } + + private static boolean isEscapable(char c) { + switch (c) { + case '!': + case '"': + case '#': + case '$': + case '%': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '.': + case '/': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '@': + case '[': + case '\\': + case ']': + case '^': + case '_': + case '`': + case '{': + case '|': + case '}': + case '~': + return true; + } + return false; + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java index 8b02e99b1..972fdef62 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -1,246 +1,10 @@ package org.commonmark.internal.util; public class Parsing { - - private static final String TAGNAME = "[A-Za-z][A-Za-z0-9-]*"; - private static final String ATTRIBUTENAME = "[a-zA-Z_:][a-zA-Z0-9:._-]*"; - private static final String UNQUOTEDVALUE = "[^\"'=<>`\\x00-\\x20]+"; - private static final String SINGLEQUOTEDVALUE = "'[^']*'"; - private static final String DOUBLEQUOTEDVALUE = "\"[^\"]*\""; - private static final String ATTRIBUTEVALUE = "(?:" + UNQUOTEDVALUE + "|" + SINGLEQUOTEDVALUE - + "|" + DOUBLEQUOTEDVALUE + ")"; - private static final String ATTRIBUTEVALUESPEC = "(?:" + "\\s*=" + "\\s*" + ATTRIBUTEVALUE - + ")"; - private static final String ATTRIBUTE = "(?:" + "\\s+" + ATTRIBUTENAME + ATTRIBUTEVALUESPEC - + "?)"; - - public static final String OPENTAG = "<" + TAGNAME + ATTRIBUTE + "*" + "\\s*/?>"; - public static final String CLOSETAG = "]"; - public static int CODE_BLOCK_INDENT = 4; public static int columnsToNextTabStop(int column) { // Tab stop is 4 return 4 - (column % 4); } - - public static int find(char c, CharSequence s, int startIndex) { - int length = s.length(); - for (int i = startIndex; i < length; i++) { - if (s.charAt(i) == c) { - return i; - } - } - return -1; - } - - public static int findLineBreak(CharSequence s, int startIndex) { - int length = s.length(); - for (int i = startIndex; i < length; i++) { - switch (s.charAt(i)) { - case '\n': - case '\r': - return i; - } - } - return -1; - } - - public static boolean isBlank(CharSequence s) { - return findNonSpace(s, 0) == -1; - } - - public static boolean hasNonSpace(CharSequence s) { - int length = s.length(); - int skipped = skip(' ', s, 0, length); - return skipped != length; - } - - public static boolean isLetter(CharSequence s, int index) { - int codePoint = Character.codePointAt(s, index); - return Character.isLetter(codePoint); - } - - public static boolean isSpaceOrTab(CharSequence s, int index) { - if (index < s.length()) { - switch (s.charAt(index)) { - case ' ': - case '\t': - return true; - } - } - return false; - } - - public static boolean isEscapable(char c) { - switch (c) { - case '!': - case '"': - case '#': - case '$': - case '%': - case '&': - case '\'': - case '(': - case ')': - case '*': - case '+': - case ',': - case '-': - case '.': - case '/': - case ':': - case ';': - case '<': - case '=': - case '>': - case '?': - case '@': - case '[': - case '\\': - case ']': - case '^': - case '_': - case '`': - case '{': - case '|': - case '}': - case '~': - return true; - } - return false; - } - - // See https://spec.commonmark.org/0.29/#punctuation-character - public static boolean isPunctuationCodePoint(int codePoint) { - switch (Character.getType(codePoint)) { - case Character.CONNECTOR_PUNCTUATION: - case Character.DASH_PUNCTUATION: - case Character.END_PUNCTUATION: - case Character.FINAL_QUOTE_PUNCTUATION: - case Character.INITIAL_QUOTE_PUNCTUATION: - case Character.OTHER_PUNCTUATION: - case Character.START_PUNCTUATION: - return true; - default: - switch (codePoint) { - case '$': - case '+': - case '<': - case '=': - case '>': - case '^': - case '`': - case '|': - case '~': - return true; - default: - return false; - } - } - } - - public static boolean isWhitespaceCodePoint(int codePoint) { - switch (codePoint) { - case ' ': - case '\t': - case '\r': - case '\n': - case '\f': - return true; - default: - return Character.getType(codePoint) == Character.SPACE_SEPARATOR; - } - } - - /** - * Prepares the input line replacing {@code \0} - */ - public static CharSequence prepareLine(CharSequence line) { - // Avoid building a new string in the majority of cases (no \0) - StringBuilder sb = null; - int length = line.length(); - for (int i = 0; i < length; i++) { - char c = line.charAt(i); - if (c == '\0') { - if (sb == null) { - sb = new StringBuilder(length); - sb.append(line, 0, i); - } - sb.append('\uFFFD'); - } else { - if (sb != null) { - sb.append(c); - } - } - } - - if (sb != null) { - return sb.toString(); - } else { - return line; - } - } - - public static int skip(char skip, CharSequence s, int startIndex, int endIndex) { - for (int i = startIndex; i < endIndex; i++) { - if (s.charAt(i) != skip) { - return i; - } - } - return endIndex; - } - - public static int skipBackwards(char skip, CharSequence s, int startIndex, int lastIndex) { - for (int i = startIndex; i >= lastIndex; i--) { - if (s.charAt(i) != skip) { - return i; - } - } - return lastIndex - 1; - } - - public static int skipSpaceTab(CharSequence s, int startIndex, int endIndex) { - for (int i = startIndex; i < endIndex; i++) { - switch (s.charAt(i)) { - case ' ': - case '\t': - break; - default: - return i; - } - } - return endIndex; - } - - public static int skipSpaceTabBackwards(CharSequence s, int startIndex, int lastIndex) { - for (int i = startIndex; i >= lastIndex; i--) { - switch (s.charAt(i)) { - case ' ': - case '\t': - break; - default: - return i; - } - } - return lastIndex - 1; - } - - private static int findNonSpace(CharSequence s, int startIndex) { - int length = s.length(); - for (int i = startIndex; i < length; i++) { - switch (s.charAt(i)) { - case ' ': - case '\t': - case '\n': - case '\u000B': - case '\f': - case '\r': - break; - default: - return i; - } - } - return -1; - } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Position.java b/commonmark/src/main/java/org/commonmark/parser/beta/Position.java similarity index 89% rename from commonmark/src/main/java/org/commonmark/internal/inline/Position.java rename to commonmark/src/main/java/org/commonmark/parser/beta/Position.java index 5f06a063a..3dbb4870f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Position.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/Position.java @@ -1,4 +1,4 @@ -package org.commonmark.internal.inline; +package org.commonmark.parser.beta; /** * Position within a {@link Scanner}. This is intentionally kept opaque so as not to expose the internal structure of diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/parser/beta/Scanner.java similarity index 99% rename from commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java rename to commonmark/src/main/java/org/commonmark/parser/beta/Scanner.java index 9de96a587..482f8eb2a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/Scanner.java @@ -1,9 +1,9 @@ -package org.commonmark.internal.inline; +package org.commonmark.parser.beta; -import org.commonmark.internal.util.CharMatcher; import org.commonmark.node.SourceSpan; import org.commonmark.parser.SourceLine; import org.commonmark.parser.SourceLines; +import org.commonmark.text.CharMatcher; import java.util.List; diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 30d6d1a5c..03fc7d759 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -1,10 +1,10 @@ package org.commonmark.renderer.markdown; -import org.commonmark.internal.util.AsciiMatcher; -import org.commonmark.internal.util.CharMatcher; -import org.commonmark.internal.util.Parsing; +import org.commonmark.text.AsciiMatcher; import org.commonmark.node.*; +import org.commonmark.text.CharMatcher; import org.commonmark.renderer.NodeRenderer; +import org.commonmark.text.Characters; import java.util.Arrays; import java.util.HashSet; @@ -268,7 +268,7 @@ public void visit(Code code) { // If it starts and ends with a space (but is not only spaces), add an additional space (otherwise they would // get removed on parsing). boolean addSpace = literal.startsWith("`") || literal.endsWith("`") || - (literal.startsWith(" ") && literal.endsWith(" ") && Parsing.hasNonSpace(literal)); + (literal.startsWith(" ") && literal.endsWith(" ") && Characters.hasNonSpace(literal)); if (addSpace) { writer.raw(' '); } @@ -416,9 +416,9 @@ private static int findMaxRunLength(char c, CharSequence s) { int backticks = 0; int start = 0; while (start < s.length()) { - int index = Parsing.find(c, s, start); + int index = Characters.find(c, s, start); if (index != -1) { - start = Parsing.skip(c, s, index + 1, s.length()); + start = Characters.skip(c, s, index + 1, s.length()); backticks = Math.max(backticks, start - index); } else { break; diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index 1231a4a73..c9f427021 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -1,6 +1,6 @@ package org.commonmark.renderer.markdown; -import org.commonmark.internal.util.CharMatcher; +import org.commonmark.text.CharMatcher; import java.io.IOException; import java.util.LinkedList; diff --git a/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java b/commonmark/src/main/java/org/commonmark/text/AsciiMatcher.java similarity index 94% rename from commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java rename to commonmark/src/main/java/org/commonmark/text/AsciiMatcher.java index dd7e8d5eb..0d9cea458 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java +++ b/commonmark/src/main/java/org/commonmark/text/AsciiMatcher.java @@ -1,8 +1,11 @@ -package org.commonmark.internal.util; +package org.commonmark.text; import java.util.BitSet; import java.util.Set; +/** + * Char matcher that can match ASCII characters efficiently. + */ public class AsciiMatcher implements CharMatcher { private final BitSet set; diff --git a/commonmark/src/main/java/org/commonmark/text/CharMatcher.java b/commonmark/src/main/java/org/commonmark/text/CharMatcher.java new file mode 100644 index 000000000..2833e65c3 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/text/CharMatcher.java @@ -0,0 +1,13 @@ +package org.commonmark.text; + +/** + * Matcher interface for {@code char} values. + *

    + * Note that because this matches on {@code char} values only (as opposed to {@code int} code points), + * this only operates on the level of code units and doesn't support supplementary characters + * (see {@link Character#isSupplementaryCodePoint(int)}). + */ +public interface CharMatcher { + + boolean matches(char c); +} diff --git a/commonmark/src/main/java/org/commonmark/text/Characters.java b/commonmark/src/main/java/org/commonmark/text/Characters.java new file mode 100644 index 000000000..6c2333c86 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/text/Characters.java @@ -0,0 +1,159 @@ +package org.commonmark.text; + +/** + * Functions for finding characters in strings or checking characters. + */ +public class Characters { + + public static int find(char c, CharSequence s, int startIndex) { + int length = s.length(); + for (int i = startIndex; i < length; i++) { + if (s.charAt(i) == c) { + return i; + } + } + return -1; + } + + public static int findLineBreak(CharSequence s, int startIndex) { + int length = s.length(); + for (int i = startIndex; i < length; i++) { + switch (s.charAt(i)) { + case '\n': + case '\r': + return i; + } + } + return -1; + } + + public static boolean isBlank(CharSequence s) { + return findNonSpace(s, 0) == -1; + } + + public static boolean hasNonSpace(CharSequence s) { + int length = s.length(); + int skipped = skip(' ', s, 0, length); + return skipped != length; + } + + public static boolean isLetter(CharSequence s, int index) { + int codePoint = Character.codePointAt(s, index); + return Character.isLetter(codePoint); + } + + public static boolean isSpaceOrTab(CharSequence s, int index) { + if (index < s.length()) { + switch (s.charAt(index)) { + case ' ': + case '\t': + return true; + } + } + return false; + } + + // See https://spec.commonmark.org/0.29/#punctuation-character + public static boolean isPunctuationCodePoint(int codePoint) { + switch (Character.getType(codePoint)) { + case Character.CONNECTOR_PUNCTUATION: + case Character.DASH_PUNCTUATION: + case Character.END_PUNCTUATION: + case Character.FINAL_QUOTE_PUNCTUATION: + case Character.INITIAL_QUOTE_PUNCTUATION: + case Character.OTHER_PUNCTUATION: + case Character.START_PUNCTUATION: + return true; + default: + switch (codePoint) { + case '$': + case '+': + case '<': + case '=': + case '>': + case '^': + case '`': + case '|': + case '~': + return true; + default: + return false; + } + } + } + + public static boolean isWhitespaceCodePoint(int codePoint) { + switch (codePoint) { + case ' ': + case '\t': + case '\r': + case '\n': + case '\f': + return true; + default: + return Character.getType(codePoint) == Character.SPACE_SEPARATOR; + } + } + + public static int skip(char skip, CharSequence s, int startIndex, int endIndex) { + for (int i = startIndex; i < endIndex; i++) { + if (s.charAt(i) != skip) { + return i; + } + } + return endIndex; + } + + public static int skipBackwards(char skip, CharSequence s, int startIndex, int lastIndex) { + for (int i = startIndex; i >= lastIndex; i--) { + if (s.charAt(i) != skip) { + return i; + } + } + return lastIndex - 1; + } + + public static int skipSpaceTab(CharSequence s, int startIndex, int endIndex) { + for (int i = startIndex; i < endIndex; i++) { + switch (s.charAt(i)) { + case ' ': + case '\t': + break; + default: + return i; + } + } + return endIndex; + } + + public static int skipSpaceTabBackwards(CharSequence s, int startIndex, int lastIndex) { + for (int i = startIndex; i >= lastIndex; i--) { + switch (s.charAt(i)) { + case ' ': + case '\t': + break; + default: + return i; + } + } + return lastIndex - 1; + } + + private static int findNonSpace(CharSequence s, int startIndex) { + int length = s.length(); + for (int i = startIndex; i < length; i++) { + switch (s.charAt(i)) { + case ' ': + case '\t': + case '\n': + case '\u000B': + case '\f': + case '\r': + break; + default: + return i; + } + } + return -1; + } +} diff --git a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java b/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java similarity index 97% rename from commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java rename to commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java index 030a765af..668f852c4 100644 --- a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java @@ -1,8 +1,10 @@ -package org.commonmark.internal.inline; +package org.commonmark.parser.beta; import org.commonmark.node.SourceSpan; import org.commonmark.parser.SourceLine; import org.commonmark.parser.SourceLines; +import org.commonmark.parser.beta.Position; +import org.commonmark.parser.beta.Scanner; import org.junit.Test; import java.util.Arrays; diff --git a/commonmark/src/test/java/org/commonmark/internal/util/ParsingTest.java b/commonmark/src/test/java/org/commonmark/text/CharactersTest.java similarity index 78% rename from commonmark/src/test/java/org/commonmark/internal/util/ParsingTest.java rename to commonmark/src/test/java/org/commonmark/text/CharactersTest.java index f51c8647b..23bf07355 100644 --- a/commonmark/src/test/java/org/commonmark/internal/util/ParsingTest.java +++ b/commonmark/src/test/java/org/commonmark/text/CharactersTest.java @@ -1,10 +1,10 @@ -package org.commonmark.internal.util; +package org.commonmark.text; import org.junit.Test; import static org.junit.Assert.assertTrue; -public class ParsingTest { +public class CharactersTest { @Test public void isPunctuation() { @@ -17,7 +17,7 @@ public void isPunctuation() { }; for (char c : chars) { - assertTrue("Expected to be punctuation: " + c, Parsing.isPunctuationCodePoint(c)); + assertTrue("Expected to be punctuation: " + c, Characters.isPunctuationCodePoint(c)); } } } diff --git a/pom.xml b/pom.xml index da06b2c2b..4eac3b7b2 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.4.1 + 3.5.0 *.internal,*.internal.* From a3d31d99fff86be3e6561e23ee66fd0310ed2adb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 11:37:13 +1100 Subject: [PATCH 056/345] Clean up Characters, Strings test util --- .../java/org/commonmark/testutil/Strings.java | 12 ----- .../markdown/CoreMarkdownNodeRenderer.java | 27 ++++------ .../java/org/commonmark/text/Characters.java | 34 +++++------- .../org/commonmark/test/PathologicalTest.java | 52 +++++++++---------- .../org/commonmark/test/SpecialInputTest.java | 5 +- .../org/commonmark/text/CharactersTest.java | 11 ++++ 6 files changed, 62 insertions(+), 79 deletions(-) delete mode 100644 commonmark-test-util/src/main/java/org/commonmark/testutil/Strings.java diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/Strings.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/Strings.java deleted file mode 100644 index ed709ed81..000000000 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/Strings.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.commonmark.testutil; - -public class Strings { - - public static String repeat(String s, int count) { - StringBuilder sb = new StringBuilder(s.length() * count); - for (int i = 0; i < count; i++) { - sb.append(s); - } - return sb.toString(); - } -} diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 03fc7d759..31a7ceb50 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -149,11 +149,12 @@ public void visit(IndentedCodeBlock indentedCodeBlock) { @Override public void visit(FencedCodeBlock fencedCodeBlock) { String literal = fencedCodeBlock.getLiteral(); - String fence = repeat(String.valueOf(fencedCodeBlock.getFenceChar()), fencedCodeBlock.getFenceLength()); + int count = fencedCodeBlock.getFenceLength(); + String fence = String.valueOf(fencedCodeBlock.getFenceChar()).repeat(count); int indent = fencedCodeBlock.getFenceIndent(); if (indent > 0) { - String indentPrefix = repeat(" ", indent); + String indentPrefix = " ".repeat(indent); writer.writePrefix(indentPrefix); writer.pushPrefix(indentPrefix); } @@ -231,18 +232,20 @@ public void visit(ListItem listItem) { boolean pushedPrefix = false; if (listHolder instanceof BulletListHolder) { BulletListHolder bulletListHolder = (BulletListHolder) listHolder; - String marker = repeat(" ", listItem.getMarkerIndent()) + bulletListHolder.bulletMarker; + int count = listItem.getMarkerIndent(); + String marker = " ".repeat(count) + bulletListHolder.bulletMarker; writer.writePrefix(marker); - writer.writePrefix(repeat(" ", contentIndent - marker.length())); - writer.pushPrefix(repeat(" ", contentIndent)); + writer.writePrefix(" ".repeat(contentIndent - marker.length())); + writer.pushPrefix(" ".repeat(contentIndent)); pushedPrefix = true; } else if (listHolder instanceof OrderedListHolder) { OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; - String marker = repeat(" ", listItem.getMarkerIndent()) + orderedListHolder.number + orderedListHolder.delimiter; + int count = listItem.getMarkerIndent(); + String marker = " ".repeat(count) + orderedListHolder.number + orderedListHolder.delimiter; orderedListHolder.number++; writer.writePrefix(marker); - writer.writePrefix(repeat(" ", contentIndent - marker.length())); - writer.pushPrefix(repeat(" ", contentIndent)); + writer.writePrefix(" ".repeat(contentIndent - marker.length())); + writer.pushPrefix(" ".repeat(contentIndent)); pushedPrefix = true; } if (listItem.getFirstChild() == null) { @@ -436,14 +439,6 @@ private static boolean contains(String s, CharMatcher charMatcher) { return false; } - private static String repeat(String s, int count) { - StringBuilder sb = new StringBuilder(s.length() * count); - for (int i = 0; i < count; i++) { - sb.append(s); - } - return sb.toString(); - } - private static List getLines(String literal) { // Without -1, split would discard all trailing empty strings, which is not what we want, e.g. it would // return the same result for "abc", "abc\n" and "abc\n\n". diff --git a/commonmark/src/main/java/org/commonmark/text/Characters.java b/commonmark/src/main/java/org/commonmark/text/Characters.java index 6c2333c86..4d9532329 100644 --- a/commonmark/src/main/java/org/commonmark/text/Characters.java +++ b/commonmark/src/main/java/org/commonmark/text/Characters.java @@ -27,8 +27,11 @@ public static int findLineBreak(CharSequence s, int startIndex) { return -1; } + /** + * @see blank line + */ public static boolean isBlank(CharSequence s) { - return findNonSpace(s, 0) == -1; + return skipSpaceTab(s, 0, s.length()) == s.length(); } public static boolean hasNonSpace(CharSequence s) { @@ -53,7 +56,9 @@ public static boolean isSpaceOrTab(CharSequence s, int index) { return false; } - // See https://spec.commonmark.org/0.29/#punctuation-character + /** + * @see punctuation character + */ public static boolean isPunctuationCodePoint(int codePoint) { switch (Character.getType(codePoint)) { case Character.CONNECTOR_PUNCTUATION: @@ -82,13 +87,18 @@ public static boolean isPunctuationCodePoint(int codePoint) { } } + /** + * Check whether the provided code point is a Unicode whitespace character as defined in the spec. + * + * @see Unicode whitespace character + */ public static boolean isWhitespaceCodePoint(int codePoint) { switch (codePoint) { case ' ': case '\t': - case '\r': case '\n': case '\f': + case '\r': return true; default: return Character.getType(codePoint) == Character.SPACE_SEPARATOR; @@ -138,22 +148,4 @@ public static int skipSpaceTabBackwards(CharSequence s, int startIndex, int last } return lastIndex - 1; } - - private static int findNonSpace(CharSequence s, int startIndex) { - int length = s.length(); - for (int i = startIndex; i < length; i++) { - switch (s.charAt(i)) { - case ' ': - case '\t': - case '\n': - case '\u000B': - case '\f': - case '\r': - break; - default: - return i; - } - } - return -1; - } } diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index a853b1b11..ae1310ed2 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -10,8 +10,6 @@ import java.util.concurrent.TimeUnit; -import static org.commonmark.testutil.Strings.repeat; - /** * Pathological input cases (from commonmark.js). */ @@ -36,58 +34,58 @@ public void nestedStrongEmphasis() { // this is limited by the stack size because visitor is recursive x = 500; assertRendering( - repeat("*a **a ", x) + "b" + repeat(" a** a*", x), - "

    " + repeat("a a ", x) + "b" + - repeat(" a a", x) + "

    \n"); + "*a **a ".repeat(x) + "b" + " a** a*".repeat(x), + "

    " + "a a ".repeat(x) + "b" + + " a a".repeat(x) + "

    \n"); } @Test public void emphasisClosersWithNoOpeners() { assertRendering( - repeat("a_ ", x), - "

    " + repeat("a_ ", x - 1) + "a_

    \n"); + "a_ ".repeat(x), + "

    " + "a_ ".repeat(x - 1) + "a_

    \n"); } @Test public void emphasisOpenersWithNoClosers() { assertRendering( - repeat("_a ", x), - "

    " + repeat("_a ", x - 1) + "_a

    \n"); + "_a ".repeat(x), + "

    " + "_a ".repeat(x - 1) + "_a

    \n"); } @Test public void linkClosersWithNoOpeners() { assertRendering( - repeat("a] ", x), - "

    " + repeat("a] ", x - 1) + "a]

    \n"); + "a] ".repeat(x), + "

    " + "a] ".repeat(x - 1) + "a]

    \n"); } @Test public void linkOpenersWithNoClosers() { assertRendering( - repeat("[a ", x), - "

    " + repeat("[a ", x - 1) + "[a

    \n"); + "[a ".repeat(x), + "

    " + "[a ".repeat(x - 1) + "[a

    \n"); } @Test public void linkOpenersAndEmphasisClosers() { assertRendering( - repeat("[ a_ ", x), - "

    " + repeat("[ a_ ", x - 1) + "[ a_

    \n"); + "[ a_ ".repeat(x), + "

    " + "[ a_ ".repeat(x - 1) + "[ a_

    \n"); } @Test public void mismatchedOpenersAndClosers() { assertRendering( - repeat("*a_ ", x), - "

    " + repeat("*a_ ", x - 1) + "*a_

    \n"); + "*a_ ".repeat(x), + "

    " + "*a_ ".repeat(x - 1) + "*a_

    \n"); } @Test public void nestedBrackets() { assertRendering( - repeat("[", x) + "a" + repeat("]", x), - "

    " + repeat("[", x) + "a" + repeat("]", x) + "

    \n"); + "[".repeat(x) + "a" + "]".repeat(x), + "

    " + "[".repeat(x) + "a" + "]".repeat(x) + "

    \n"); } @Test @@ -95,29 +93,29 @@ public void nestedBlockQuotes() { // this is limited by the stack size because visitor is recursive x = 1000; assertRendering( - repeat("> ", x) + "a\n", - repeat("
    \n", x) + "

    a

    \n" + - repeat("
    \n", x)); + "> ".repeat(x) + "a\n", + "
    \n".repeat(x) + "

    a

    \n" + + "
    \n".repeat(x)); } @Test public void hugeHorizontalRule() { assertRendering( - repeat("*", 10000) + "\n", + "*".repeat(10000) + "\n", "
    \n"); } @Test public void backslashInLink() { // See https://github.com/commonmark/commonmark.js/issues/157 - assertRendering("[" + repeat("\\", x) + "\n", - "

    " + "[" + repeat("\\", x / 2) + "

    \n"); + assertRendering("[" + "\\".repeat(x) + "\n", + "

    " + "[" + "\\".repeat(x / 2) + "

    \n"); } @Test public void unclosedInlineLinks() { // See https://github.com/commonmark/commonmark.js/issues/129 - assertRendering(repeat("[](", x) + "\n", - "

    " + repeat("[](", x) + "

    \n"); + assertRendering("[](".repeat(x) + "\n", + "

    " + "[](".repeat(x) + "

    \n"); } } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 8eba1dfe3..770a1aa21 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -1,6 +1,5 @@ package org.commonmark.test; -import org.commonmark.testutil.Strings; import org.junit.Test; public class SpecialInputTest extends CoreRenderingTestCase { @@ -95,14 +94,14 @@ public void linkLabelWithBracket() { @Test public void linkLabelLength() { - String label1 = Strings.repeat("a", 999); + String label1 = "a".repeat(999); assertRendering("[foo][" + label1 + "]\n\n[" + label1 + "]: /", "

    foo

    \n"); assertRendering("[foo][x" + label1 + "]\n\n[x" + label1 + "]: /", "

    [foo][x" + label1 + "]

    \n

    [x" + label1 + "]: /

    \n"); assertRendering("[foo][\n" + label1 + "]\n\n[\n" + label1 + "]: /", "

    [foo][\n" + label1 + "]

    \n

    [\n" + label1 + "]: /

    \n"); - String label2 = Strings.repeat("a\n", 499); + String label2 = "a\n".repeat(499); assertRendering("[foo][" + label2 + "]\n\n[" + label2 + "]: /", "

    foo

    \n"); assertRendering("[foo][12" + label2 + "]\n\n[12" + label2 + "]: /", "

    [foo][12" + label2 + "]

    \n

    [12" + label2 + "]: /

    \n"); diff --git a/commonmark/src/test/java/org/commonmark/text/CharactersTest.java b/commonmark/src/test/java/org/commonmark/text/CharactersTest.java index 23bf07355..a362cf53c 100644 --- a/commonmark/src/test/java/org/commonmark/text/CharactersTest.java +++ b/commonmark/src/test/java/org/commonmark/text/CharactersTest.java @@ -2,6 +2,7 @@ import org.junit.Test; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class CharactersTest { @@ -20,4 +21,14 @@ public void isPunctuation() { assertTrue("Expected to be punctuation: " + c, Characters.isPunctuationCodePoint(c)); } } + + @Test + public void isBlank() { + assertTrue(Characters.isBlank("")); + assertTrue(Characters.isBlank(" ")); + assertTrue(Characters.isBlank("\t")); + assertTrue(Characters.isBlank(" \t")); + assertFalse(Characters.isBlank("a")); + assertFalse(Characters.isBlank("\f")); + } } From fde5cc68ec8944eb428bbc09152e1d46e2221683 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 11:45:52 +1100 Subject: [PATCH 057/345] Add package-info for new packages --- .../main/java/org/commonmark/parser/beta/package-info.java | 4 ++++ .../src/main/java/org/commonmark/text/package-info.java | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 commonmark/src/main/java/org/commonmark/parser/beta/package-info.java create mode 100644 commonmark/src/main/java/org/commonmark/text/package-info.java diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/package-info.java b/commonmark/src/main/java/org/commonmark/parser/beta/package-info.java new file mode 100644 index 000000000..029d80507 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/beta/package-info.java @@ -0,0 +1,4 @@ +/** + * Experimental APIs to use for extensions. APIs are subject to change if necessary. + */ +package org.commonmark.parser.beta; diff --git a/commonmark/src/main/java/org/commonmark/text/package-info.java b/commonmark/src/main/java/org/commonmark/text/package-info.java new file mode 100644 index 000000000..ab9eec6f1 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/text/package-info.java @@ -0,0 +1,4 @@ +/** + * Text processing utilities for parsing and rendering, exported for use by extensions + */ +package org.commonmark.text; From 554c717f1e82829e10280fec8be8a7822aab290c Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 12:03:11 +1100 Subject: [PATCH 058/345] Bump Android gradle, tools versions --- commonmark-android-test/README.md | 2 +- commonmark-android-test/app/build.gradle | 9 +- commonmark-android-test/build.gradle | 6 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 58695 -> 43462 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- commonmark-android-test/gradlew | 282 +++++++++++------- commonmark-android-test/gradlew.bat | 58 ++-- 7 files changed, 211 insertions(+), 150 deletions(-) diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index ed0201725..0fb792ae3 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -6,7 +6,7 @@ Current `minSdk` is 19 Requirements: -* Java 7 or above +* Java 11 or above * Android SDK 30 Configuration diff --git a/commonmark-android-test/app/build.gradle b/commonmark-android-test/app/build.gradle index 1b39c87c8..5b7cc4cd6 100644 --- a/commonmark-android-test/app/build.gradle +++ b/commonmark-android-test/app/build.gradle @@ -1,8 +1,9 @@ apply plugin: 'com.android.application' android { + namespace "org.commonmark.android.test" compileSdkVersion 30 - buildToolsVersion "30.0.2" + buildToolsVersion "34.0.0" defaultConfig { applicationId "org.commonmark.android.test" @@ -13,8 +14,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } packagingOptions { @@ -41,5 +42,5 @@ android { } dependencies { - implementation('org.nibor.autolink:autolink:0.10.0') + implementation('org.nibor.autolink:autolink:0.11.0') } diff --git a/commonmark-android-test/build.gradle b/commonmark-android-test/build.gradle index 4de776573..3ac119f44 100644 --- a/commonmark-android-test/build.gradle +++ b/commonmark-android-test/build.gradle @@ -1,16 +1,16 @@ buildscript { repositories { - jcenter() + mavenCentral() google() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.2' + classpath 'com.android.tools.build:gradle:8.2.2' } } allprojects { repositories { - jcenter() + mavenCentral() google() } } diff --git a/commonmark-android-test/gradle/wrapper/gradle-wrapper.jar b/commonmark-android-test/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2faf2fc91d853cd5d4242b5547257070..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 zcma&NWl&^owk(X(xVyW%ySuwf;qI=D6|RlDJ2cR^yEKh!@I- zp9QeisK*rlxC>+~7Dk4IxIRsKBHqdR9b3+fyL=ynHmIDe&|>O*VlvO+%z5;9Z$|DJ zb4dO}-R=MKr^6EKJiOrJdLnCJn>np?~vU-1sSFgPu;pthGwf}bG z(1db%xwr#x)r+`4AGu$j7~u2MpVs3VpLp|mx&;>`0p0vH6kF+D2CY0fVdQOZ@h;A` z{infNyvmFUiu*XG}RNMNwXrbec_*a3N=2zJ|Wh5z* z5rAX$JJR{#zP>KY**>xHTuw?|-Rg|o24V)74HcfVT;WtQHXlE+_4iPE8QE#DUm%x0 zEKr75ur~W%w#-My3Tj`hH6EuEW+8K-^5P62$7Sc5OK+22qj&Pd1;)1#4tKihi=~8C zHiQSst0cpri6%OeaR`PY>HH_;CPaRNty%WTm4{wDK8V6gCZlG@U3$~JQZ;HPvDJcT1V{ z?>H@13MJcCNe#5z+MecYNi@VT5|&UiN1D4ATT+%M+h4c$t;C#UAs3O_q=GxK0}8%8 z8J(_M9bayxN}69ex4dzM_P3oh@ZGREjVvn%%r7=xjkqxJP4kj}5tlf;QosR=%4L5y zWhgejO=vao5oX%mOHbhJ8V+SG&K5dABn6!WiKl{|oPkq(9z8l&Mm%(=qGcFzI=eLu zWc_oCLyf;hVlB@dnwY98?75B20=n$>u3b|NB28H0u-6Rpl((%KWEBOfElVWJx+5yg z#SGqwza7f}$z;n~g%4HDU{;V{gXIhft*q2=4zSezGK~nBgu9-Q*rZ#2f=Q}i2|qOp z!!y4p)4o=LVUNhlkp#JL{tfkhXNbB=Ox>M=n6soptJw-IDI|_$is2w}(XY>a=H52d z3zE$tjPUhWWS+5h=KVH&uqQS=$v3nRs&p$%11b%5qtF}S2#Pc`IiyBIF4%A!;AVoI zXU8-Rpv!DQNcF~(qQnyyMy=-AN~U>#&X1j5BLDP{?K!%h!;hfJI>$mdLSvktEr*89 zdJHvby^$xEX0^l9g$xW-d?J;L0#(`UT~zpL&*cEh$L|HPAu=P8`OQZV!-}l`noSp_ zQ-1$q$R-gDL)?6YaM!=8H=QGW$NT2SeZlb8PKJdc=F-cT@j7Xags+Pr*jPtlHFnf- zh?q<6;)27IdPc^Wdy-mX%2s84C1xZq9Xms+==F4);O`VUASmu3(RlgE#0+#giLh-& zcxm3_e}n4{%|X zJp{G_j+%`j_q5}k{eW&TlP}J2wtZ2^<^E(O)4OQX8FDp6RJq!F{(6eHWSD3=f~(h} zJXCf7=r<16X{pHkm%yzYI_=VDP&9bmI1*)YXZeB}F? z(%QsB5fo*FUZxK$oX~X^69;x~j7ms8xlzpt-T15e9}$4T-pC z6PFg@;B-j|Ywajpe4~bk#S6(fO^|mm1hKOPfA%8-_iGCfICE|=P_~e;Wz6my&)h_~ zkv&_xSAw7AZ%ThYF(4jADW4vg=oEdJGVOs>FqamoL3Np8>?!W#!R-0%2Bg4h?kz5I zKV-rKN2n(vUL%D<4oj@|`eJ>0i#TmYBtYmfla;c!ATW%;xGQ0*TW@PTlGG><@dxUI zg>+3SiGdZ%?5N=8uoLA|$4isK$aJ%i{hECP$bK{J#0W2gQ3YEa zZQ50Stn6hqdfxJ*9#NuSLwKFCUGk@c=(igyVL;;2^wi4o30YXSIb2g_ud$ zgpCr@H0qWtk2hK8Q|&wx)}4+hTYlf;$a4#oUM=V@Cw#!$(nOFFpZ;0lc!qd=c$S}Z zGGI-0jg~S~cgVT=4Vo)b)|4phjStD49*EqC)IPwyeKBLcN;Wu@Aeph;emROAwJ-0< z_#>wVm$)ygH|qyxZaet&(Vf%pVdnvKWJn9`%DAxj3ot;v>S$I}jJ$FLBF*~iZ!ZXE zkvui&p}fI0Y=IDX)mm0@tAd|fEHl~J&K}ZX(Mm3cm1UAuwJ42+AO5@HwYfDH7ipIc zmI;1J;J@+aCNG1M`Btf>YT>~c&3j~Qi@Py5JT6;zjx$cvOQW@3oQ>|}GH?TW-E z1R;q^QFjm5W~7f}c3Ww|awg1BAJ^slEV~Pk`Kd`PS$7;SqJZNj->it4DW2l15}xP6 zoCl$kyEF%yJni0(L!Z&14m!1urXh6Btj_5JYt1{#+H8w?5QI%% zo-$KYWNMJVH?Hh@1n7OSu~QhSswL8x0=$<8QG_zepi_`y_79=nK=_ZP_`Em2UI*tyQoB+r{1QYZCpb?2OrgUw#oRH$?^Tj!Req>XiE#~B|~ z+%HB;=ic+R@px4Ld8mwpY;W^A%8%l8$@B@1m5n`TlKI6bz2mp*^^^1mK$COW$HOfp zUGTz-cN9?BGEp}5A!mDFjaiWa2_J2Iq8qj0mXzk; z66JBKRP{p%wN7XobR0YjhAuW9T1Gw3FDvR5dWJ8ElNYF94eF3ebu+QwKjtvVu4L zI9ip#mQ@4uqVdkl-TUQMb^XBJVLW(-$s;Nq;@5gr4`UfLgF$adIhd?rHOa%D);whv z=;krPp~@I+-Z|r#s3yCH+c1US?dnm+C*)r{m+86sTJusLdNu^sqLrfWed^ndHXH`m zd3#cOe3>w-ga(Dus_^ppG9AC>Iq{y%%CK+Cro_sqLCs{VLuK=dev>OL1dis4(PQ5R zcz)>DjEkfV+MO;~>VUlYF00SgfUo~@(&9$Iy2|G0T9BSP?&T22>K46D zL*~j#yJ?)^*%J3!16f)@Y2Z^kS*BzwfAQ7K96rFRIh>#$*$_Io;z>ux@}G98!fWR@ zGTFxv4r~v)Gsd|pF91*-eaZ3Qw1MH$K^7JhWIdX%o$2kCbvGDXy)a?@8T&1dY4`;L z4Kn+f%SSFWE_rpEpL9bnlmYq`D!6F%di<&Hh=+!VI~j)2mfil03T#jJ_s?}VV0_hp z7T9bWxc>Jm2Z0WMU?`Z$xE74Gu~%s{mW!d4uvKCx@WD+gPUQ zV0vQS(Ig++z=EHN)BR44*EDSWIyT~R4$FcF*VEY*8@l=218Q05D2$|fXKFhRgBIEE zdDFB}1dKkoO^7}{5crKX!p?dZWNz$m>1icsXG2N+((x0OIST9Zo^DW_tytvlwXGpn zs8?pJXjEG;T@qrZi%#h93?FP$!&P4JA(&H61tqQi=opRzNpm zkrG}$^t9&XduK*Qa1?355wd8G2CI6QEh@Ua>AsD;7oRUNLPb76m4HG3K?)wF~IyS3`fXuNM>${?wmB zpVz;?6_(Fiadfd{vUCBM*_kt$+F3J+IojI;9L(gc9n3{sEZyzR9o!_mOwFC#tQ{Q~ zP3-`#uK#tP3Q7~Q;4H|wjZHO8h7e4IuBxl&vz2w~D8)w=Wtg31zpZhz%+kzSzL*dV zwp@{WU4i;hJ7c2f1O;7Mz6qRKeASoIv0_bV=i@NMG*l<#+;INk-^`5w@}Dj~;k=|}qM1vq_P z|GpBGe_IKq|LNy9SJhKOQ$c=5L{Dv|Q_lZl=-ky*BFBJLW9&y_C|!vyM~rQx=!vun z?rZJQB5t}Dctmui5i31C_;_}CEn}_W%>oSXtt>@kE1=JW*4*v4tPp;O6 zmAk{)m!)}34pTWg8{i>($%NQ(Tl;QC@J@FfBoc%Gr&m560^kgSfodAFrIjF}aIw)X zoXZ`@IsMkc8_=w%-7`D6Y4e*CG8k%Ud=GXhsTR50jUnm+R*0A(O3UKFg0`K;qp1bl z7``HN=?39ic_kR|^R^~w-*pa?Vj#7|e9F1iRx{GN2?wK!xR1GW!qa=~pjJb-#u1K8 zeR?Y2i-pt}yJq;SCiVHODIvQJX|ZJaT8nO+(?HXbLefulKKgM^B(UIO1r+S=7;kLJ zcH}1J=Px2jsh3Tec&v8Jcbng8;V-`#*UHt?hB(pmOipKwf3Lz8rG$heEB30Sg*2rx zV<|KN86$soN(I!BwO`1n^^uF2*x&vJ$2d$>+`(romzHP|)K_KkO6Hc>_dwMW-M(#S zK(~SiXT1@fvc#U+?|?PniDRm01)f^#55;nhM|wi?oG>yBsa?~?^xTU|fX-R(sTA+5 zaq}-8Tx7zrOy#3*JLIIVsBmHYLdD}!0NP!+ITW+Thn0)8SS!$@)HXwB3tY!fMxc#1 zMp3H?q3eD?u&Njx4;KQ5G>32+GRp1Ee5qMO0lZjaRRu&{W<&~DoJNGkcYF<5(Ab+J zgO>VhBl{okDPn78<%&e2mR{jwVCz5Og;*Z;;3%VvoGo_;HaGLWYF7q#jDX=Z#Ml`H z858YVV$%J|e<1n`%6Vsvq7GmnAV0wW4$5qQ3uR@1i>tW{xrl|ExywIc?fNgYlA?C5 zh$ezAFb5{rQu6i7BSS5*J-|9DQ{6^BVQ{b*lq`xS@RyrsJN?-t=MTMPY;WYeKBCNg z^2|pN!Q^WPJuuO4!|P@jzt&tY1Y8d%FNK5xK(!@`jO2aEA*4 zkO6b|UVBipci?){-Ke=+1;mGlND8)6+P;8sq}UXw2hn;fc7nM>g}GSMWu&v&fqh

    iViYT=fZ(|3Ox^$aWPp4a8h24tD<|8-!aK0lHgL$N7Efw}J zVIB!7=T$U`ao1?upi5V4Et*-lTG0XvExbf!ya{cua==$WJyVG(CmA6Of*8E@DSE%L z`V^$qz&RU$7G5mg;8;=#`@rRG`-uS18$0WPN@!v2d{H2sOqP|!(cQ@ zUHo!d>>yFArLPf1q`uBvY32miqShLT1B@gDL4XoVTK&@owOoD)OIHXrYK-a1d$B{v zF^}8D3Y^g%^cnvScOSJR5QNH+BI%d|;J;wWM3~l>${fb8DNPg)wrf|GBP8p%LNGN# z3EaIiItgwtGgT&iYCFy9-LG}bMI|4LdmmJt@V@% zb6B)1kc=T)(|L@0;wr<>=?r04N;E&ef+7C^`wPWtyQe(*pD1pI_&XHy|0gIGHMekd zF_*M4yi6J&Z4LQj65)S zXwdM{SwUo%3SbPwFsHgqF@V|6afT|R6?&S;lw=8% z3}@9B=#JI3@B*#4s!O))~z zc>2_4Q_#&+5V`GFd?88^;c1i7;Vv_I*qt!_Yx*n=;rj!82rrR2rQ8u5(Ejlo{15P% zs~!{%XJ>FmJ})H^I9bn^Re&38H{xA!0l3^89k(oU;bZWXM@kn$#aoS&Y4l^-WEn-fH39Jb9lA%s*WsKJQl?n9B7_~P z-XM&WL7Z!PcoF6_D>V@$CvUIEy=+Z&0kt{szMk=f1|M+r*a43^$$B^MidrT0J;RI` z(?f!O<8UZkm$_Ny$Hth1J#^4ni+im8M9mr&k|3cIgwvjAgjH z8`N&h25xV#v*d$qBX5jkI|xOhQn!>IYZK7l5#^P4M&twe9&Ey@@GxYMxBZq2e7?`q z$~Szs0!g{2fGcp9PZEt|rdQ6bhAgpcLHPz?f-vB?$dc*!9OL?Q8mn7->bFD2Si60* z!O%y)fCdMSV|lkF9w%x~J*A&srMyYY3{=&$}H zGQ4VG_?$2X(0|vT0{=;W$~icCI{b6W{B!Q8xdGhF|D{25G_5_+%s(46lhvNLkik~R z>nr(&C#5wwOzJZQo9m|U<;&Wk!_#q|V>fsmj1g<6%hB{jGoNUPjgJslld>xmODzGjYc?7JSuA?A_QzjDw5AsRgi@Y|Z0{F{!1=!NES-#*f^s4l0Hu zz468))2IY5dmD9pa*(yT5{EyP^G>@ZWumealS-*WeRcZ}B%gxq{MiJ|RyX-^C1V=0 z@iKdrGi1jTe8Ya^x7yyH$kBNvM4R~`fbPq$BzHum-3Zo8C6=KW@||>zsA8-Y9uV5V z#oq-f5L5}V<&wF4@X@<3^C%ptp6+Ce)~hGl`kwj)bsAjmo_GU^r940Z-|`<)oGnh7 zFF0Tde3>ui?8Yj{sF-Z@)yQd~CGZ*w-6p2U<8}JO-sRsVI5dBji`01W8A&3$?}lxBaC&vn0E$c5tW* zX>5(zzZ=qn&!J~KdsPl;P@bmA-Pr8T*)eh_+Dv5=Ma|XSle6t(k8qcgNyar{*ReQ8 zTXwi=8vr>!3Ywr+BhggHDw8ke==NTQVMCK`$69fhzEFB*4+H9LIvdt-#IbhZvpS}} zO3lz;P?zr0*0$%-Rq_y^k(?I{Mk}h@w}cZpMUp|ucs55bcloL2)($u%mXQw({Wzc~ z;6nu5MkjP)0C(@%6Q_I_vsWrfhl7Zpoxw#WoE~r&GOSCz;_ro6i(^hM>I$8y>`!wW z*U^@?B!MMmb89I}2(hcE4zN2G^kwyWCZp5JG>$Ez7zP~D=J^LMjSM)27_0B_X^C(M z`fFT+%DcKlu?^)FCK>QzSnV%IsXVcUFhFdBP!6~se&xxrIxsvySAWu++IrH;FbcY$ z2DWTvSBRfLwdhr0nMx+URA$j3i7_*6BWv#DXfym?ZRDcX9C?cY9sD3q)uBDR3uWg= z(lUIzB)G$Hr!){>E{s4Dew+tb9kvToZp-1&c?y2wn@Z~(VBhqz`cB;{E4(P3N2*nJ z_>~g@;UF2iG{Kt(<1PyePTKahF8<)pozZ*xH~U-kfoAayCwJViIrnqwqO}7{0pHw$ zs2Kx?s#vQr7XZ264>5RNKSL8|Ty^=PsIx^}QqOOcfpGUU4tRkUc|kc7-!Ae6!+B{o~7nFpm3|G5^=0#Bnm6`V}oSQlrX(u%OWnC zoLPy&Q;1Jui&7ST0~#+}I^&?vcE*t47~Xq#YwvA^6^} z`WkC)$AkNub|t@S!$8CBlwbV~?yp&@9h{D|3z-vJXgzRC5^nYm+PyPcgRzAnEi6Q^gslXYRv4nycsy-SJu?lMps-? zV`U*#WnFsdPLL)Q$AmD|0`UaC4ND07+&UmOu!eHruzV|OUox<+Jl|Mr@6~C`T@P%s zW7sgXLF2SSe9Fl^O(I*{9wsFSYb2l%-;&Pi^dpv!{)C3d0AlNY6!4fgmSgj_wQ*7Am7&$z;Jg&wgR-Ih;lUvWS|KTSg!&s_E9_bXBkZvGiC6bFKDWZxsD$*NZ#_8bl zG1P-#@?OQzED7@jlMJTH@V!6k;W>auvft)}g zhoV{7$q=*;=l{O>Q4a@ ziMjf_u*o^PsO)#BjC%0^h>Xp@;5$p{JSYDt)zbb}s{Kbt!T*I@Pk@X0zds6wsefuU zW$XY%yyRGC94=6mf?x+bbA5CDQ2AgW1T-jVAJbm7K(gp+;v6E0WI#kuACgV$r}6L? zd|Tj?^%^*N&b>Dd{Wr$FS2qI#Ucs1yd4N+RBUQiSZGujH`#I)mG&VKoDh=KKFl4=G z&MagXl6*<)$6P}*Tiebpz5L=oMaPrN+caUXRJ`D?=K9!e0f{@D&cZLKN?iNP@X0aF zE(^pl+;*T5qt?1jRC=5PMgV!XNITRLS_=9{CJExaQj;lt!&pdzpK?8p>%Mb+D z?yO*uSung=-`QQ@yX@Hyd4@CI^r{2oiu`%^bNkz+Nkk!IunjwNC|WcqvX~k=><-I3 zDQdbdb|!v+Iz01$w@aMl!R)koD77Xp;eZwzSl-AT zr@Vu{=xvgfq9akRrrM)}=!=xcs+U1JO}{t(avgz`6RqiiX<|hGG1pmop8k6Q+G_mv zJv|RfDheUp2L3=^C=4aCBMBn0aRCU(DQwX-W(RkRwmLeuJYF<0urcaf(=7)JPg<3P zQs!~G)9CT18o!J4{zX{_e}4eS)U-E)0FAt}wEI(c0%HkxgggW;(1E=>J17_hsH^sP z%lT0LGgbUXHx-K*CI-MCrP66UP0PvGqM$MkeLyqHdbgP|_Cm!7te~b8p+e6sQ_3k| zVcwTh6d83ltdnR>D^)BYQpDKlLk3g0Hdcgz2}%qUs9~~Rie)A-BV1mS&naYai#xcZ z(d{8=-LVpTp}2*y)|gR~;qc7fp26}lPcLZ#=JpYcn3AT9(UIdOyg+d(P5T7D&*P}# zQCYplZO5|7+r19%9e`v^vfSS1sbX1c%=w1;oyruXB%Kl$ACgKQ6=qNWLsc=28xJjg zwvsI5-%SGU|3p>&zXVl^vVtQT3o-#$UT9LI@Npz~6=4!>mc431VRNN8od&Ul^+G_kHC`G=6WVWM z%9eWNyy(FTO|A+@x}Ou3CH)oi;t#7rAxdIXfNFwOj_@Y&TGz6P_sqiB`Q6Lxy|Q{`|fgmRG(k+!#b*M+Z9zFce)f-7;?Km5O=LHV9f9_87; zF7%R2B+$?@sH&&-$@tzaPYkw0;=i|;vWdI|Wl3q_Zu>l;XdIw2FjV=;Mq5t1Q0|f< zs08j54Bp`3RzqE=2enlkZxmX6OF+@|2<)A^RNQpBd6o@OXl+i)zO%D4iGiQNuXd+zIR{_lb96{lc~bxsBveIw6umhShTX+3@ZJ=YHh@ zWY3(d0azg;7oHn>H<>?4@*RQbi>SmM=JrHvIG(~BrvI)#W(EAeO6fS+}mxxcc+X~W6&YVl86W9WFSS}Vz-f9vS?XUDBk)3TcF z8V?$4Q)`uKFq>xT=)Y9mMFVTUk*NIA!0$?RP6Ig0TBmUFrq*Q-Agq~DzxjStQyJ({ zBeZ;o5qUUKg=4Hypm|}>>L=XKsZ!F$yNTDO)jt4H0gdQ5$f|d&bnVCMMXhNh)~mN z@_UV6D7MVlsWz+zM+inZZp&P4fj=tm6fX)SG5H>OsQf_I8c~uGCig$GzuwViK54bcgL;VN|FnyQl>Ed7(@>=8$a_UKIz|V6CeVSd2(P z0Uu>A8A+muM%HLFJQ9UZ5c)BSAv_zH#1f02x?h9C}@pN@6{>UiAp>({Fn(T9Q8B z^`zB;kJ5b`>%dLm+Ol}ty!3;8f1XDSVX0AUe5P#@I+FQ-`$(a;zNgz)4x5hz$Hfbg z!Q(z26wHLXko(1`;(BAOg_wShpX0ixfWq3ponndY+u%1gyX)_h=v1zR#V}#q{au6; z!3K=7fQwnRfg6FXtNQmP>`<;!N137paFS%y?;lb1@BEdbvQHYC{976l`cLqn;b8lp zIDY>~m{gDj(wfnK!lpW6pli)HyLEiUrNc%eXTil|F2s(AY+LW5hkKb>TQ3|Q4S9rr zpDs4uK_co6XPsn_z$LeS{K4jFF`2>U`tbgKdyDne`xmR<@6AA+_hPNKCOR-Zqv;xk zu5!HsBUb^!4uJ7v0RuH-7?l?}b=w5lzzXJ~gZcxRKOovSk@|#V+MuX%Y+=;14i*%{)_gSW9(#4%)AV#3__kac1|qUy!uyP{>?U#5wYNq}y$S9pCc zFc~4mgSC*G~j0u#qqp9 z${>3HV~@->GqEhr_Xwoxq?Hjn#=s2;i~g^&Hn|aDKpA>Oc%HlW(KA1?BXqpxB;Ydx)w;2z^MpjJ(Qi(X!$5RC z*P{~%JGDQqojV>2JbEeCE*OEu!$XJ>bWA9Oa_Hd;y)F%MhBRi*LPcdqR8X`NQ&1L# z5#9L*@qxrx8n}LfeB^J{%-?SU{FCwiWyHp682F+|pa+CQa3ZLzBqN1{)h4d6+vBbV zC#NEbQLC;}me3eeYnOG*nXOJZEU$xLZ1<1Y=7r0(-U0P6-AqwMAM`a(Ed#7vJkn6plb4eI4?2y3yOTGmmDQ!z9`wzbf z_OY#0@5=bnep;MV0X_;;SJJWEf^E6Bd^tVJ9znWx&Ks8t*B>AM@?;D4oWUGc z!H*`6d7Cxo6VuyS4Eye&L1ZRhrRmN6Lr`{NL(wDbif|y&z)JN>Fl5#Wi&mMIr5i;x zBx}3YfF>>8EC(fYnmpu~)CYHuHCyr5*`ECap%t@y=jD>!_%3iiE|LN$mK9>- zHdtpy8fGZtkZF?%TW~29JIAfi2jZT8>OA7=h;8T{{k?c2`nCEx9$r zS+*&vt~2o^^J+}RDG@+9&M^K*z4p{5#IEVbz`1%`m5c2};aGt=V?~vIM}ZdPECDI)47|CWBCfDWUbxBCnmYivQ*0Nu_xb*C>~C9(VjHM zxe<*D<#dQ8TlpMX2c@M<9$w!RP$hpG4cs%AI){jp*Sj|*`m)5(Bw*A0$*i-(CA5#%>a)$+jI2C9r6|(>J8InryENI z$NohnxDUB;wAYDwrb*!N3noBTKPpPN}~09SEL18tkG zxgz(RYU_;DPT{l?Q$+eaZaxnsWCA^ds^0PVRkIM%bOd|G2IEBBiz{&^JtNsODs;5z zICt_Zj8wo^KT$7Bg4H+y!Df#3mbl%%?|EXe!&(Vmac1DJ*y~3+kRKAD=Ovde4^^%~ zw<9av18HLyrf*_>Slp;^i`Uy~`mvBjZ|?Ad63yQa#YK`4+c6;pW4?XIY9G1(Xh9WO8{F-Aju+nS9Vmv=$Ac0ienZ+p9*O%NG zMZKy5?%Z6TAJTE?o5vEr0r>f>hb#2w2U3DL64*au_@P!J!TL`oH2r*{>ffu6|A7tv zL4juf$DZ1MW5ZPsG!5)`k8d8c$J$o;%EIL0va9&GzWvkS%ZsGb#S(?{!UFOZ9<$a| zY|a+5kmD5N&{vRqkgY>aHsBT&`rg|&kezoD)gP0fsNYHsO#TRc_$n6Lf1Z{?+DLziXlHrq4sf(!>O{?Tj;Eh@%)+nRE_2VxbN&&%%caU#JDU%vL3}Cb zsb4AazPI{>8H&d=jUaZDS$-0^AxE@utGs;-Ez_F(qC9T=UZX=>ok2k2 ziTn{K?y~a5reD2A)P${NoI^>JXn>`IeArow(41c-Wm~)wiryEP(OS{YXWi7;%dG9v zI?mwu1MxD{yp_rrk!j^cKM)dc4@p4Ezyo%lRN|XyD}}>v=Xoib0gOcdXrQ^*61HNj z=NP|pd>@yfvr-=m{8$3A8TQGMTE7g=z!%yt`8`Bk-0MMwW~h^++;qyUP!J~ykh1GO z(FZ59xuFR$(WE;F@UUyE@Sp>`aVNjyj=Ty>_Vo}xf`e7`F;j-IgL5`1~-#70$9_=uBMq!2&1l zomRgpD58@)YYfvLtPW}{C5B35R;ZVvB<<#)x%srmc_S=A7F@DW8>QOEGwD6suhwCg z>Pa+YyULhmw%BA*4yjDp|2{!T98~<6Yfd(wo1mQ!KWwq0eg+6)o1>W~f~kL<-S+P@$wx*zeI|1t7z#Sxr5 zt6w+;YblPQNplq4Z#T$GLX#j6yldXAqj>4gAnnWtBICUnA&-dtnlh=t0Ho_vEKwV` z)DlJi#!@nkYV#$!)@>udAU*hF?V`2$Hf=V&6PP_|r#Iv*J$9)pF@X3`k;5})9^o4y z&)~?EjX5yX12O(BsFy-l6}nYeuKkiq`u9145&3Ssg^y{5G3Pse z9w(YVa0)N-fLaBq1`P!_#>SS(8fh_5!f{UrgZ~uEdeMJIz7DzI5!NHHqQtm~#CPij z?=N|J>nPR6_sL7!f4hD_|KH`vf8(Wpnj-(gPWH+ZvID}%?~68SwhPTC3u1_cB`otq z)U?6qo!ZLi5b>*KnYHWW=3F!p%h1;h{L&(Q&{qY6)_qxNfbP6E3yYpW!EO+IW3?@J z);4>g4gnl^8klu7uA>eGF6rIGSynacogr)KUwE_R4E5Xzi*Qir@b-jy55-JPC8c~( zo!W8y9OGZ&`xmc8;=4-U9=h{vCqfCNzYirONmGbRQlR`WWlgnY+1wCXbMz&NT~9*| z6@FrzP!LX&{no2!Ln_3|I==_4`@}V?4a;YZKTdw;vT<+K+z=uWbW(&bXEaWJ^W8Td z-3&1bY^Z*oM<=M}LVt>_j+p=2Iu7pZmbXrhQ_k)ysE9yXKygFNw$5hwDn(M>H+e1&9BM5!|81vd%r%vEm zqxY3?F@fb6O#5UunwgAHR9jp_W2zZ}NGp2%mTW@(hz7$^+a`A?mb8|_G*GNMJ) zjqegXQio=i@AINre&%ofexAr95aop5C+0MZ0m-l=MeO8m3epm7U%vZB8+I+C*iNFM z#T3l`gknX;D$-`2XT^Cg*vrv=RH+P;_dfF++cP?B_msQI4j+lt&rX2)3GaJx%W*Nn zkML%D{z5tpHH=dksQ*gzc|}gzW;lwAbxoR07VNgS*-c3d&8J|;@3t^ zVUz*J*&r7DFRuFVDCJDK8V9NN5hvpgGjwx+5n)qa;YCKe8TKtdnh{I7NU9BCN!0dq zczrBk8pE{{@vJa9ywR@mq*J=v+PG;?fwqlJVhijG!3VmIKs>9T6r7MJpC)m!Tc#>g zMtVsU>wbwFJEfwZ{vB|ZlttNe83)$iz`~#8UJ^r)lJ@HA&G#}W&ZH*;k{=TavpjWE z7hdyLZPf*X%Gm}i`Y{OGeeu^~nB8=`{r#TUrM-`;1cBvEd#d!kPqIgYySYhN-*1;L z^byj%Yi}Gx)Wnkosi337BKs}+5H5dth1JA{Ir-JKN$7zC)*}hqeoD(WfaUDPT>0`- z(6sa0AoIqASwF`>hP}^|)a_j2s^PQn*qVC{Q}htR z5-)duBFXT_V56-+UohKXlq~^6uf!6sA#ttk1o~*QEy_Y-S$gAvq47J9Vtk$5oA$Ct zYhYJ@8{hsC^98${!#Ho?4y5MCa7iGnfz}b9jE~h%EAAv~Qxu)_rAV;^cygV~5r_~?l=B`zObj7S=H=~$W zPtI_m%g$`kL_fVUk9J@>EiBH zOO&jtn~&`hIFMS5S`g8w94R4H40mdNUH4W@@XQk1sr17b{@y|JB*G9z1|CrQjd+GX z6+KyURG3;!*BQrentw{B2R&@2&`2}n(z-2&X7#r!{yg@Soy}cRD~j zj9@UBW+N|4HW4AWapy4wfUI- zZ`gSL6DUlgj*f1hSOGXG0IVH8HxK?o2|3HZ;KW{K+yPAlxtb)NV_2AwJm|E)FRs&& z=c^e7bvUsztY|+f^k7NXs$o1EUq>cR7C0$UKi6IooHWlK_#?IWDkvywnzg&ThWo^? z2O_N{5X39#?eV9l)xI(>@!vSB{DLt*oY!K1R8}_?%+0^C{d9a%N4 zoxHVT1&Lm|uDX%$QrBun5e-F`HJ^T$ zmzv)p@4ZHd_w9!%Hf9UYNvGCw2TTTbrj9pl+T9%-_-}L(tES>Or-}Z4F*{##n3~L~TuxjirGuIY#H7{%$E${?p{Q01 zi6T`n;rbK1yIB9jmQNycD~yZq&mbIsFWHo|ZAChSFPQa<(%d8mGw*V3fh|yFoxOOiWJd(qvVb!Z$b88cg->N=qO*4k~6;R==|9ihg&riu#P~s4Oap9O7f%crSr^rljeIfXDEg>wi)&v*a%7zpz<9w z*r!3q9J|390x`Zk;g$&OeN&ctp)VKRpDSV@kU2Q>jtok($Y-*x8_$2piTxun81@vt z!Vj?COa0fg2RPXMSIo26T=~0d`{oGP*eV+$!0I<(4azk&Vj3SiG=Q!6mX0p$z7I}; z9BJUFgT-K9MQQ-0@Z=^7R<{bn2Fm48endsSs`V7_@%8?Bxkqv>BDoVcj?K#dV#uUP zL1ND~?D-|VGKe3Rw_7-Idpht>H6XRLh*U7epS6byiGvJpr%d}XwfusjH9g;Z98H`x zyde%%5mhGOiL4wljCaWCk-&uE4_OOccb9c!ZaWt4B(wYl!?vyzl%7n~QepN&eFUrw zFIOl9c({``6~QD+43*_tzP{f2x41h(?b43^y6=iwyB)2os5hBE!@YUS5?N_tXd=h( z)WE286Fbd>R4M^P{!G)f;h<3Q>Fipuy+d2q-)!RyTgt;wr$(?9ox3;q+{E*ZQHhOn;lM`cjnu9 zXa48ks-v(~b*;MAI<>YZH(^NV8vjb34beE<_cwKlJoR;k6lJNSP6v}uiyRD?|0w+X@o1ONrH8a$fCxXpf? z?$DL0)7|X}Oc%h^zrMKWc-NS9I0Utu@>*j}b@tJ=ixQSJ={4@854wzW@E>VSL+Y{i z#0b=WpbCZS>kUCO_iQz)LoE>P5LIG-hv9E+oG}DtlIDF>$tJ1aw9^LuhLEHt?BCj& z(O4I8v1s#HUi5A>nIS-JK{v!7dJx)^Yg%XjNmlkWAq2*cv#tHgz`Y(bETc6CuO1VkN^L-L3j_x<4NqYb5rzrLC-7uOv z!5e`GZt%B782C5-fGnn*GhDF$%(qP<74Z}3xx+{$4cYKy2ikxI7B2N+2r07DN;|-T->nU&!=Cm#rZt%O_5c&1Z%nlWq3TKAW0w zQqemZw_ue--2uKQsx+niCUou?HjD`xhEjjQd3%rrBi82crq*~#uA4+>vR<_S{~5ce z-2EIl?~s z1=GVL{NxP1N3%=AOaC}j_Fv=ur&THz zyO!d9kHq|c73kpq`$+t+8Bw7MgeR5~`d7ChYyGCBWSteTB>8WAU(NPYt2Dk`@#+}= zI4SvLlyk#pBgVigEe`?NG*vl7V6m+<}%FwPV=~PvvA)=#ths==DRTDEYh4V5}Cf$z@#;< zyWfLY_5sP$gc3LLl2x+Ii)#b2nhNXJ{R~vk`s5U7Nyu^3yFg&D%Txwj6QezMX`V(x z=C`{76*mNb!qHHs)#GgGZ_7|vkt9izl_&PBrsu@}L`X{95-2jf99K)0=*N)VxBX2q z((vkpP2RneSIiIUEnGb?VqbMb=Zia+rF~+iqslydE34cSLJ&BJW^3knX@M;t*b=EA zNvGzv41Ld_T+WT#XjDB840vovUU^FtN_)G}7v)1lPetgpEK9YS^OWFkPoE{ovj^=@ zO9N$S=G$1ecndT_=5ehth2Lmd1II-PuT~C9`XVePw$y8J#dpZ?Tss<6wtVglm(Ok7 z3?^oi@pPio6l&!z8JY(pJvG=*pI?GIOu}e^EB6QYk$#FJQ%^AIK$I4epJ+9t?KjqA+bkj&PQ*|vLttme+`9G=L% ziadyMw_7-M)hS(3E$QGNCu|o23|%O+VN7;Qggp?PB3K-iSeBa2b}V4_wY`G1Jsfz4 z9|SdB^;|I8E8gWqHKx!vj_@SMY^hLEIbSMCuE?WKq=c2mJK z8LoG-pnY!uhqFv&L?yEuxo{dpMTsmCn)95xanqBrNPTgXP((H$9N${Ow~Is-FBg%h z53;|Y5$MUN)9W2HBe2TD`ct^LHI<(xWrw}$qSoei?}s)&w$;&!14w6B6>Yr6Y8b)S z0r71`WmAvJJ`1h&poLftLUS6Ir zC$bG9!Im_4Zjse)#K=oJM9mHW1{%l8sz$1o?ltdKlLTxWWPB>Vk22czVt|1%^wnN@*!l)}?EgtvhC>vlHm^t+ogpgHI1_$1ox9e;>0!+b(tBrmXRB`PY1vp-R**8N7 zGP|QqI$m(Rdu#=(?!(N}G9QhQ%o!aXE=aN{&wtGP8|_qh+7a_j_sU5|J^)vxq;# zjvzLn%_QPHZZIWu1&mRAj;Sa_97p_lLq_{~j!M9N^1yp3U_SxRqK&JnR%6VI#^E12 z>CdOVI^_9aPK2eZ4h&^{pQs}xsijXgFYRIxJ~N7&BB9jUR1fm!(xl)mvy|3e6-B3j zJn#ajL;bFTYJ2+Q)tDjx=3IklO@Q+FFM}6UJr6km7hj7th9n_&JR7fnqC!hTZoM~T zBeaVFp%)0cbPhejX<8pf5HyRUj2>aXnXBqDJe73~J%P(2C?-RT{c3NjE`)om! zl$uewSgWkE66$Kb34+QZZvRn`fob~Cl9=cRk@Es}KQm=?E~CE%spXaMO6YmrMl%9Q zlA3Q$3|L1QJ4?->UjT&CBd!~ru{Ih^in&JXO=|<6J!&qp zRe*OZ*cj5bHYlz!!~iEKcuE|;U4vN1rk$xq6>bUWD*u(V@8sG^7>kVuo(QL@Ki;yL zWC!FT(q{E8#on>%1iAS0HMZDJg{Z{^!De(vSIq&;1$+b)oRMwA3nc3mdTSG#3uYO_ z>+x;7p4I;uHz?ZB>dA-BKl+t-3IB!jBRgdvAbW!aJ(Q{aT>+iz?91`C-xbe)IBoND z9_Xth{6?(y3rddwY$GD65IT#f3<(0o#`di{sh2gm{dw*#-Vnc3r=4==&PU^hCv$qd zjw;>i&?L*Wq#TxG$mFIUf>eK+170KG;~+o&1;Tom9}}mKo23KwdEM6UonXgc z!6N(@k8q@HPw{O8O!lAyi{rZv|DpgfU{py+j(X_cwpKqcalcqKIr0kM^%Br3SdeD> zHSKV94Yxw;pjzDHo!Q?8^0bb%L|wC;4U^9I#pd5O&eexX+Im{ z?jKnCcsE|H?{uGMqVie_C~w7GX)kYGWAg%-?8|N_1#W-|4F)3YTDC+QSq1s!DnOML3@d`mG%o2YbYd#jww|jD$gotpa)kntakp#K;+yo-_ZF9qrNZw<%#C zuPE@#3RocLgPyiBZ+R_-FJ_$xP!RzWm|aN)S+{$LY9vvN+IW~Kf3TsEIvP+B9Mtm! zpfNNxObWQpLoaO&cJh5>%slZnHl_Q~(-Tfh!DMz(dTWld@LG1VRF`9`DYKhyNv z2pU|UZ$#_yUx_B_|MxUq^glT}O5Xt(Vm4Mr02><%C)@v;vPb@pT$*yzJ4aPc_FZ3z z3}PLoMBIM>q_9U2rl^sGhk1VUJ89=*?7|v`{!Z{6bqFMq(mYiA?%KbsI~JwuqVA9$H5vDE+VocjX+G^%bieqx->s;XWlKcuv(s%y%D5Xbc9+ zc(_2nYS1&^yL*ey664&4`IoOeDIig}y-E~_GS?m;D!xv5-xwz+G`5l6V+}CpeJDi^ z%4ed$qowm88=iYG+(`ld5Uh&>Dgs4uPHSJ^TngXP_V6fPyl~>2bhi20QB%lSd#yYn zO05?KT1z@?^-bqO8Cg`;ft>ilejsw@2%RR7;`$Vs;FmO(Yr3Fp`pHGr@P2hC%QcA|X&N2Dn zYf`MqXdHi%cGR@%y7Rg7?d3?an){s$zA{!H;Ie5exE#c~@NhQUFG8V=SQh%UxUeiV zd7#UcYqD=lk-}sEwlpu&H^T_V0{#G?lZMxL7ih_&{(g)MWBnCZxtXg znr#}>U^6!jA%e}@Gj49LWG@*&t0V>Cxc3?oO7LSG%~)Y5}f7vqUUnQ;STjdDU}P9IF9d9<$;=QaXc zL1^X7>fa^jHBu_}9}J~#-oz3Oq^JmGR#?GO7b9a(=R@fw@}Q{{@`Wy1vIQ#Bw?>@X z-_RGG@wt|%u`XUc%W{J z>iSeiz8C3H7@St3mOr_mU+&bL#Uif;+Xw-aZdNYUpdf>Rvu0i0t6k*}vwU`XNO2he z%miH|1tQ8~ZK!zmL&wa3E;l?!!XzgV#%PMVU!0xrDsNNZUWKlbiOjzH-1Uoxm8E#r`#2Sz;-o&qcqB zC-O_R{QGuynW14@)7&@yw1U}uP(1cov)twxeLus0s|7ayrtT8c#`&2~Fiu2=R;1_4bCaD=*E@cYI>7YSnt)nQc zohw5CsK%m?8Ack)qNx`W0_v$5S}nO|(V|RZKBD+btO?JXe|~^Qqur%@eO~<8-L^9d z=GA3-V14ng9L29~XJ>a5k~xT2152zLhM*@zlp2P5Eu}bywkcqR;ISbas&#T#;HZSf z2m69qTV(V@EkY(1Dk3`}j)JMo%ZVJ*5eB zYOjIisi+igK0#yW*gBGj?@I{~mUOvRFQR^pJbEbzFxTubnrw(Muk%}jI+vXmJ;{Q6 zrSobKD>T%}jV4Ub?L1+MGOD~0Ir%-`iTnWZN^~YPrcP5y3VMAzQ+&en^VzKEb$K!Q z<7Dbg&DNXuow*eD5yMr+#08nF!;%4vGrJI++5HdCFcGLfMW!KS*Oi@=7hFwDG!h2< zPunUEAF+HncQkbfFj&pbzp|MU*~60Z(|Ik%Tn{BXMN!hZOosNIseT?R;A`W?=d?5X zK(FB=9mZusYahp|K-wyb={rOpdn=@;4YI2W0EcbMKyo~-#^?h`BA9~o285%oY zfifCh5Lk$SY@|2A@a!T2V+{^!psQkx4?x0HSV`(w9{l75QxMk!)U52Lbhn{8ol?S) zCKo*7R(z!uk<6*qO=wh!Pul{(qq6g6xW;X68GI_CXp`XwO zxuSgPRAtM8K7}5E#-GM!*ydOOG_{A{)hkCII<|2=ma*71ci_-}VPARm3crFQjLYV! z9zbz82$|l01mv`$WahE2$=fAGWkd^X2kY(J7iz}WGS z@%MyBEO=A?HB9=^?nX`@nh;7;laAjs+fbo!|K^mE!tOB>$2a_O0y-*uaIn8k^6Y zSbuv;5~##*4Y~+y7Z5O*3w4qgI5V^17u*ZeupVGH^nM&$qmAk|anf*>r zWc5CV;-JY-Z@Uq1Irpb^O`L_7AGiqd*YpGUShb==os$uN3yYvb`wm6d=?T*it&pDk zo`vhw)RZX|91^^Wa_ti2zBFyWy4cJu#g)_S6~jT}CC{DJ_kKpT`$oAL%b^!2M;JgT zM3ZNbUB?}kP(*YYvXDIH8^7LUxz5oE%kMhF!rnPqv!GiY0o}NR$OD=ITDo9r%4E>E0Y^R(rS^~XjWyVI6 zMOR5rPXhTp*G*M&X#NTL`Hu*R+u*QNoiOKg4CtNPrjgH>c?Hi4MUG#I917fx**+pJfOo!zFM&*da&G_x)L(`k&TPI*t3e^{crd zX<4I$5nBQ8Ax_lmNRa~E*zS-R0sxkz`|>7q_?*e%7bxqNm3_eRG#1ae3gtV9!fQpY z+!^a38o4ZGy9!J5sylDxZTx$JmG!wg7;>&5H1)>f4dXj;B+@6tMlL=)cLl={jLMxY zbbf1ax3S4>bwB9-$;SN2?+GULu;UA-35;VY*^9Blx)Jwyb$=U!D>HhB&=jSsd^6yw zL)?a|>GxU!W}ocTC(?-%z3!IUhw^uzc`Vz_g>-tv)(XA#JK^)ZnC|l1`@CdX1@|!| z_9gQ)7uOf?cR@KDp97*>6X|;t@Y`k_N@)aH7gY27)COv^P3ya9I{4z~vUjLR9~z1Z z5=G{mVtKH*&$*t0@}-i_v|3B$AHHYale7>E+jP`ClqG%L{u;*ff_h@)al?RuL7tOO z->;I}>%WI{;vbLP3VIQ^iA$4wl6@0sDj|~112Y4OFjMs`13!$JGkp%b&E8QzJw_L5 zOnw9joc0^;O%OpF$Qp)W1HI!$4BaXX84`%@#^dk^hFp^pQ@rx4g(8Xjy#!X%+X5Jd@fs3amGT`}mhq#L97R>OwT5-m|h#yT_-v@(k$q7P*9X~T*3)LTdzP!*B} z+SldbVWrrwQo9wX*%FyK+sRXTa@O?WM^FGWOE?S`R(0P{<6p#f?0NJvnBia?k^fX2 zNQs7K-?EijgHJY}&zsr;qJ<*PCZUd*x|dD=IQPUK_nn)@X4KWtqoJNHkT?ZWL_hF? zS8lp2(q>;RXR|F;1O}EE#}gCrY~#n^O`_I&?&z5~7N;zL0)3Tup`%)oHMK-^r$NT% zbFg|o?b9w(q@)6w5V%si<$!U<#}s#x@0aX-hP>zwS#9*75VXA4K*%gUc>+yzupTDBOKH8WR4V0pM(HrfbQ&eJ79>HdCvE=F z|J>s;;iDLB^3(9}?biKbxf1$lI!*Z%*0&8UUq}wMyPs_hclyQQi4;NUY+x2qy|0J; zhn8;5)4ED1oHwg+VZF|80<4MrL97tGGXc5Sw$wAI#|2*cvQ=jB5+{AjMiDHmhUC*a zlmiZ`LAuAn_}hftXh;`Kq0zblDk8?O-`tnilIh|;3lZp@F_osJUV9`*R29M?7H{Fy z`nfVEIDIWXmU&YW;NjU8)EJpXhxe5t+scf|VXM!^bBlwNh)~7|3?fWwo_~ZFk(22% zTMesYw+LNx3J-_|DM~`v93yXe=jPD{q;li;5PD?Dyk+b? zo21|XpT@)$BM$%F=P9J19Vi&1#{jM3!^Y&fr&_`toi`XB1!n>sbL%U9I5<7!@?t)~ z;&H%z>bAaQ4f$wIzkjH70;<8tpUoxzKrPhn#IQfS%9l5=Iu))^XC<58D!-O z{B+o5R^Z21H0T9JQ5gNJnqh#qH^na|z92=hONIM~@_iuOi|F>jBh-?aA20}Qx~EpDGElELNn~|7WRXRFnw+Wdo`|# zBpU=Cz3z%cUJ0mx_1($X<40XEIYz(`noWeO+x#yb_pwj6)R(__%@_Cf>txOQ74wSJ z0#F3(zWWaR-jMEY$7C*3HJrohc79>MCUu26mfYN)f4M~4gD`}EX4e}A!U}QV8!S47 z6y-U-%+h`1n`*pQuKE%Av0@)+wBZr9mH}@vH@i{v(m-6QK7Ncf17x_D=)32`FOjjo zg|^VPf5c6-!FxN{25dvVh#fog=NNpXz zfB$o+0jbRkHH{!TKhE709f+jI^$3#v1Nmf80w`@7-5$1Iv_`)W^px8P-({xwb;D0y z7LKDAHgX<84?l!I*Dvi2#D@oAE^J|g$3!)x1Ua;_;<@#l1fD}lqU2_tS^6Ht$1Wl} zBESo7o^)9-Tjuz$8YQSGhfs{BQV6zW7dA?0b(Dbt=UnQs&4zHfe_sj{RJ4uS-vQpC zX;Bbsuju4%!o8?&m4UZU@~ZZjeFF6ex2ss5_60_JS_|iNc+R0GIjH1@Z z=rLT9%B|WWgOrR7IiIwr2=T;Ne?30M!@{%Qf8o`!>=s<2CBpCK_TWc(DX51>e^xh8 z&@$^b6CgOd7KXQV&Y4%}_#uN*mbanXq(2=Nj`L7H7*k(6F8s6{FOw@(DzU`4-*77{ zF+dxpv}%mFpYK?>N_2*#Y?oB*qEKB}VoQ@bzm>ptmVS_EC(#}Lxxx730trt0G)#$b zE=wVvtqOct1%*9}U{q<)2?{+0TzZzP0jgf9*)arV)*e!f`|jgT{7_9iS@e)recI#z zbzolURQ+TOzE!ymqvBY7+5NnAbWxvMLsLTwEbFqW=CPyCsmJ}P1^V30|D5E|p3BC5 z)3|qgw@ra7aXb-wsa|l^in~1_fm{7bS9jhVRkYVO#U{qMp z)Wce+|DJ}4<2gp8r0_xfZpMo#{Hl2MfjLcZdRB9(B(A(f;+4s*FxV{1F|4d`*sRNd zp4#@sEY|?^FIJ;tmH{@keZ$P(sLh5IdOk@k^0uB^BWr@pk6mHy$qf&~rI>P*a;h0C{%oA*i!VjWn&D~O#MxN&f@1Po# zKN+ zrGrkSjcr?^R#nGl<#Q722^wbYcgW@{+6CBS<1@%dPA8HC!~a`jTz<`g_l5N1M@9wn9GOAZ>nqNgq!yOCbZ@1z`U_N`Z>}+1HIZxk*5RDc&rd5{3qjRh8QmT$VyS;jK z;AF+r6XnnCp=wQYoG|rT2@8&IvKq*IB_WvS%nt%e{MCFm`&W*#LXc|HrD?nVBo=(8*=Aq?u$sDA_sC_RPDUiQ+wnIJET8vx$&fxkW~kP9qXKt zozR)@xGC!P)CTkjeWvXW5&@2?)qt)jiYWWBU?AUtzAN}{JE1I)dfz~7$;}~BmQF`k zpn11qmObXwRB8&rnEG*#4Xax3XBkKlw(;tb?Np^i+H8m(Wyz9k{~ogba@laiEk;2! zV*QV^6g6(QG%vX5Um#^sT&_e`B1pBW5yVth~xUs#0}nv?~C#l?W+9Lsb_5)!71rirGvY zTIJ$OPOY516Y|_014sNv+Z8cc5t_V=i>lWV=vNu#!58y9Zl&GsMEW#pPYPYGHQ|;vFvd*9eM==$_=vc7xnyz0~ zY}r??$<`wAO?JQk@?RGvkWVJlq2dk9vB(yV^vm{=NVI8dhsX<)O(#nr9YD?I?(VmQ z^r7VfUBn<~p3()8yOBjm$#KWx!5hRW)5Jl7wY@ky9lNM^jaT##8QGVsYeaVywmpv>X|Xj7gWE1Ezai&wVLt3p)k4w~yrskT-!PR!kiyQlaxl(( zXhF%Q9x}1TMt3~u@|#wWm-Vq?ZerK={8@~&@9r5JW}r#45#rWii};t`{5#&3$W)|@ zbAf2yDNe0q}NEUvq_Quq3cTjcw z@H_;$hu&xllCI9CFDLuScEMg|x{S7GdV8<&Mq=ezDnRZAyX-8gv97YTm0bg=d)(>N z+B2FcqvI9>jGtnK%eO%y zoBPkJTk%y`8TLf4)IXPBn`U|9>O~WL2C~C$z~9|0m*YH<-vg2CD^SX#&)B4ngOSG$ zV^wmy_iQk>dfN@Pv(ckfy&#ak@MLC7&Q6Ro#!ezM*VEh`+b3Jt%m(^T&p&WJ2Oqvj zs-4nq0TW6cv~(YI$n0UkfwN}kg3_fp?(ijSV#tR9L0}l2qjc7W?i*q01=St0eZ=4h zyGQbEw`9OEH>NMuIe)hVwYHsGERWOD;JxEiO7cQv%pFCeR+IyhwQ|y@&^24k+|8fD zLiOWFNJ2&vu2&`Jv96_z-Cd5RLgmeY3*4rDOQo?Jm`;I_(+ejsPM03!ly!*Cu}Cco zrQSrEDHNyzT(D5s1rZq!8#?f6@v6dB7a-aWs(Qk>N?UGAo{gytlh$%_IhyL7h?DLXDGx zgxGEBQoCAWo-$LRvM=F5MTle`M})t3vVv;2j0HZY&G z22^iGhV@uaJh(XyyY%} zd4iH_UfdV#T=3n}(Lj^|n;O4|$;xhu*8T3hR1mc_A}fK}jfZ7LX~*n5+`8N2q#rI$ z@<_2VANlYF$vIH$ zl<)+*tIWW78IIINA7Rr7i{<;#^yzxoLNkXL)eSs=%|P>$YQIh+ea_3k z_s7r4%j7%&*NHSl?R4k%1>Z=M9o#zxY!n8sL5>BO-ZP;T3Gut>iLS@U%IBrX6BA3k z)&@q}V8a{X<5B}K5s(c(LQ=%v1ocr`t$EqqY0EqVjr65usa=0bkf|O#ky{j3)WBR(((L^wmyHRzoWuL2~WTC=`yZ zn%VX`L=|Ok0v7?s>IHg?yArBcync5rG#^+u)>a%qjES%dRZoIyA8gQ;StH z1Ao7{<&}6U=5}4v<)1T7t!J_CL%U}CKNs-0xWoTTeqj{5{?Be$L0_tk>M9o8 zo371}S#30rKZFM{`H_(L`EM9DGp+Mifk&IP|C2Zu_)Ghr4Qtpmkm1osCf@%Z$%t+7 zYH$Cr)Ro@3-QDeQJ8m+x6%;?YYT;k6Z0E-?kr>x33`H%*ueBD7Zx~3&HtWn0?2Wt} zTG}*|v?{$ajzt}xPzV%lL1t-URi8*Zn)YljXNGDb>;!905Td|mpa@mHjIH%VIiGx- zd@MqhpYFu4_?y5N4xiHn3vX&|e6r~Xt> zZG`aGq|yTNjv;9E+Txuoa@A(9V7g?1_T5FzRI;!=NP1Kqou1z5?%X~Wwb{trRfd>i z8&y^H)8YnKyA_Fyx>}RNmQIczT?w2J4SNvI{5J&}Wto|8FR(W;Qw#b1G<1%#tmYzQ zQ2mZA-PAdi%RQOhkHy9Ea#TPSw?WxwL@H@cbkZwIq0B!@ns}niALidmn&W?!Vd4Gj zO7FiuV4*6Mr^2xlFSvM;Cp_#r8UaqIzHJQg_z^rEJw&OMm_8NGAY2)rKvki|o1bH~ z$2IbfVeY2L(^*rMRU1lM5Y_sgrDS`Z??nR2lX;zyR=c%UyGb*%TC-Dil?SihkjrQy~TMv6;BMs7P8il`H7DmpVm@rJ;b)hW)BL)GjS154b*xq-NXq2cwE z^;VP7ua2pxvCmxrnqUYQMH%a%nHmwmI33nJM(>4LznvY*k&C0{8f*%?zggpDgkuz&JBx{9mfb@wegEl2v!=}Sq2Gaty0<)UrOT0{MZtZ~j5y&w zXlYa_jY)I_+VA-^#mEox#+G>UgvM!Ac8zI<%JRXM_73Q!#i3O|)lOP*qBeJG#BST0 zqohi)O!|$|2SeJQo(w6w7%*92S})XfnhrH_Z8qe!G5>CglP=nI7JAOW?(Z29;pXJ9 zR9`KzQ=WEhy*)WH>$;7Cdz|>*i>=##0bB)oU0OR>>N<21e4rMCHDemNi2LD>Nc$;& zQRFthpWniC1J6@Zh~iJCoLOxN`oCKD5Q4r%ynwgUKPlIEd#?QViIqovY|czyK8>6B zSP%{2-<;%;1`#0mG^B(8KbtXF;Nf>K#Di72UWE4gQ%(_26Koiad)q$xRL~?pN71ZZ zujaaCx~jXjygw;rI!WB=xrOJO6HJ!!w}7eiivtCg5K|F6$EXa)=xUC za^JXSX98W`7g-tm@uo|BKj39Dl;sg5ta;4qjo^pCh~{-HdLl6qI9Ix6f$+qiZ$}s= zNguKrU;u+T@ko(Vr1>)Q%h$?UKXCY>3se%&;h2osl2D zE4A9bd7_|^njDd)6cI*FupHpE3){4NQ*$k*cOWZ_?CZ>Z4_fl@n(mMnYK62Q1d@+I zr&O))G4hMihgBqRIAJkLdk(p(D~X{-oBUA+If@B}j& zsHbeJ3RzTq96lB7d($h$xTeZ^gP0c{t!Y0c)aQE;$FY2!mACg!GDEMKXFOPI^)nHZ z`aSPJpvV0|bbrzhWWkuPURlDeN%VT8tndV8?d)eN*i4I@u zVKl^6{?}A?P)Fsy?3oi#clf}L18t;TjNI2>eI&(ezDK7RyqFxcv%>?oxUlonv(px) z$vnPzRH`y5A(x!yOIfL0bmgeMQB$H5wenx~!ujQK*nUBW;@Em&6Xv2%s(~H5WcU2R z;%Nw<$tI)a`Ve!>x+qegJnQsN2N7HaKzrFqM>`6R*gvh%O*-%THt zrB$Nk;lE;z{s{r^PPm5qz(&lM{sO*g+W{sK+m3M_z=4=&CC>T`{X}1Vg2PEfSj2x_ zmT*(x;ov%3F?qoEeeM>dUn$a*?SIGyO8m806J1W1o+4HRhc2`9$s6hM#qAm zChQ87b~GEw{ADfs+5}FJ8+|bIlIv(jT$Ap#hSHoXdd9#w<#cA<1Rkq^*EEkknUd4& zoIWIY)sAswy6fSERVm&!SO~#iN$OgOX*{9@_BWFyJTvC%S++ilSfCrO(?u=Dc?CXZ zzCG&0yVR{Z`|ZF0eEApWEo#s9osV>F{uK{QA@BES#&;#KsScf>y zvs?vIbI>VrT<*!;XmQS=bhq%46-aambZ(8KU-wOO2=en~D}MCToB_u;Yz{)1ySrPZ z@=$}EvjTdzTWU7c0ZI6L8=yP+YRD_eMMos}b5vY^S*~VZysrkq<`cK3>>v%uy7jgq z0ilW9KjVDHLv0b<1K_`1IkbTOINs0=m-22c%M~l=^S}%hbli-3?BnNq?b`hx^HX2J zIe6ECljRL0uBWb`%{EA=%!i^4sMcj+U_TaTZRb+~GOk z^ZW!nky0n*Wb*r+Q|9H@ml@Z5gU&W`(z4-j!OzC1wOke`TRAYGZVl$PmQ16{3196( zO*?`--I}Qf(2HIwb2&1FB^!faPA2=sLg(@6P4mN)>Dc3i(B0;@O-y2;lM4akD>@^v z=u>*|!s&9zem70g7zfw9FXl1bpJW(C#5w#uy5!V?Q(U35A~$dR%LDVnq@}kQm13{} zd53q3N(s$Eu{R}k2esbftfjfOITCL;jWa$}(mmm}d(&7JZ6d3%IABCapFFYjdEjdK z&4Edqf$G^MNAtL=uCDRs&Fu@FXRgX{*0<(@c3|PNHa>L%zvxWS={L8%qw`STm+=Rd zA}FLspESSIpE_^41~#5yI2bJ=9`oc;GIL!JuW&7YetZ?0H}$$%8rW@*J37L-~Rsx!)8($nI4 zZhcZ2^=Y+p4YPl%j!nFJA|*M^gc(0o$i3nlphe+~-_m}jVkRN{spFs(o0ajW@f3K{ zDV!#BwL322CET$}Y}^0ixYj2w>&Xh12|R8&yEw|wLDvF!lZ#dOTHM9pK6@Nm-@9Lnng4ZHBgBSrr7KI8YCC9DX5Kg|`HsiwJHg2(7#nS;A{b3tVO?Z% za{m5b3rFV6EpX;=;n#wltDv1LE*|g5pQ+OY&*6qCJZc5oDS6Z6JD#6F)bWxZSF@q% z+1WV;m!lRB!n^PC>RgQCI#D1br_o^#iPk>;K2hB~0^<~)?p}LG%kigm@moD#q3PE+ zA^Qca)(xnqw6x>XFhV6ku9r$E>bWNrVH9fum0?4s?Rn2LG{Vm_+QJHse6xa%nzQ?k zKug4PW~#Gtb;#5+9!QBgyB@q=sk9=$S{4T>wjFICStOM?__fr+Kei1 z3j~xPqW;W@YkiUM;HngG!;>@AITg}vAE`M2Pj9Irl4w1fo4w<|Bu!%rh%a(Ai^Zhi zs92>v5;@Y(Zi#RI*ua*h`d_7;byQSa*v9E{2x$<-_=5Z<7{%)}4XExANcz@rK69T0x3%H<@frW>RA8^swA+^a(FxK| zFl3LD*ImHN=XDUkrRhp6RY5$rQ{bRgSO*(vEHYV)3Mo6Jy3puiLmU&g82p{qr0F?ohmbz)f2r{X2|T2 z$4fdQ=>0BeKbiVM!e-lIIs8wVTuC_m7}y4A_%ikI;Wm5$9j(^Y z(cD%U%k)X>_>9~t8;pGzL6L-fmQO@K; zo&vQzMlgY95;1BSkngY)e{`n0!NfVgf}2mB3t}D9@*N;FQ{HZ3Pb%BK6;5#-O|WI( zb6h@qTLU~AbVW#_6?c!?Dj65Now7*pU{h!1+eCV^KCuPAGs28~3k@ueL5+u|Z-7}t z9|lskE`4B7W8wMs@xJa{#bsCGDFoRSNSnmNYB&U7 zVGKWe%+kFB6kb)e;TyHfqtU6~fRg)f|>=5(N36)0+C z`hv65J<$B}WUc!wFAb^QtY31yNleq4dzmG`1wHTj=c*=hay9iD071Hc?oYoUk|M*_ zU1GihAMBsM@5rUJ(qS?9ZYJ6@{bNqJ`2Mr+5#hKf?doa?F|+^IR!8lq9)wS3tF_9n zW_?hm)G(M+MYb?V9YoX^_mu5h-LP^TL^!Q9Z7|@sO(rg_4+@=PdI)WL(B7`!K^ND- z-uIuVDCVEdH_C@c71YGYT^_Scf_dhB8Z2Xy6vGtBSlYud9vggOqv^L~F{BraSE_t} zIkP+Hp2&nH^-MNEs}^`oMLy11`PQW$T|K(`Bu*(f@)mv1-qY(_YG&J2M2<7k;;RK~ zL{Fqj9yCz8(S{}@c)S!65aF<=&eLI{hAMErCx&>i7OeDN>okvegO87OaG{Jmi<|}D zaT@b|0X{d@OIJ7zvT>r+eTzgLq~|Dpu)Z&db-P4z*`M$UL51lf>FLlq6rfG)%doyp z)3kk_YIM!03eQ8Vu_2fg{+osaEJPtJ-s36R+5_AEG12`NG)IQ#TF9c@$99%0iye+ zUzZ57=m2)$D(5Nx!n)=5Au&O0BBgwxIBaeI(mro$#&UGCr<;C{UjJVAbVi%|+WP(a zL$U@TYCxJ=1{Z~}rnW;7UVb7+ZnzgmrogDxhjLGo>c~MiJAWs&&;AGg@%U?Y^0JhL ze(x6Z74JG6FlOFK(T}SXQfhr}RIFl@QXKnIcXYF)5|V~e-}suHILKT-k|<*~Ij|VF zC;t@=uj=hot~*!C68G8hTA%8SzOfETOXQ|3FSaIEjvBJp(A)7SWUi5!Eu#yWgY+;n zlm<$+UDou*V+246_o#V4kMdto8hF%%Lki#zPh}KYXmMf?hrN0;>Mv%`@{0Qn`Ujp) z=lZe+13>^Q!9zT);H<(#bIeRWz%#*}sgUX9P|9($kexOyKIOc`dLux}c$7It4u|Rl z6SSkY*V~g_B-hMPo_ak>>z@AVQ(_N)VY2kB3IZ0G(iDUYw+2d7W^~(Jq}KY=JnWS( z#rzEa&0uNhJ>QE8iiyz;n2H|SV#Og+wEZv=f2%1ELX!SX-(d3tEj$5$1}70Mp<&eI zCkfbByL7af=qQE@5vDVxx1}FSGt_a1DoE3SDI+G)mBAna)KBG4p8Epxl9QZ4BfdAN zFnF|Y(umr;gRgG6NLQ$?ZWgllEeeq~z^ZS7L?<(~O&$5|y)Al^iMKy}&W+eMm1W z7EMU)u^ke(A1#XCV>CZ71}P}0x)4wtHO8#JRG3MA-6g=`ZM!FcICCZ{IEw8Dm2&LQ z1|r)BUG^0GzI6f946RrBlfB1Vs)~8toZf~7)+G;pv&XiUO(%5bm)pl=p>nV^o*;&T z;}@oZSibzto$arQgfkp|z4Z($P>dTXE{4O=vY0!)kDO* zGF8a4wq#VaFpLfK!iELy@?-SeRrdz%F*}hjKcA*y@mj~VD3!it9lhRhX}5YOaR9$} z3mS%$2Be7{l(+MVx3 z(4?h;P!jnRmX9J9sYN#7i=iyj_5q7n#X(!cdqI2lnr8T$IfOW<_v`eB!d9xY1P=2q&WtOXY=D9QYteP)De?S4}FK6#6Ma z=E*V+#s8>L;8aVroK^6iKo=MH{4yEZ_>N-N z`(|;aOATba1^asjxlILk<4}f~`39dBFlxj>Dw(hMYKPO3EEt1@S`1lxFNM+J@uB7T zZ8WKjz7HF1-5&2=l=fqF-*@>n5J}jIxdDwpT?oKM3s8Nr`x8JnN-kCE?~aM1H!hAE z%%w(3kHfGwMnMmNj(SU(w42OrC-euI>Dsjk&jz3ts}WHqmMpzQ3vZrsXrZ|}+MHA7 z068obeXZTsO*6RS@o3x80E4ok``rV^Y3hr&C1;|ZZ0|*EKO`$lECUYG2gVFtUTw)R z4Um<0ZzlON`zTdvVdL#KFoMFQX*a5wM0Czp%wTtfK4Sjs)P**RW&?lP$(<}q%r68Z zS53Y!d@&~ne9O)A^tNrXHhXBkj~$8j%pT1%%mypa9AW5E&s9)rjF4@O3ytH{0z6riz|@< zB~UPh*wRFg2^7EbQrHf0y?E~dHlkOxof_a?M{LqQ^C!i2dawHTPYUE=X@2(3<=OOxs8qn_(y>pU>u^}3y&df{JarR0@VJn0f+U%UiF=$Wyq zQvnVHESil@d|8&R<%}uidGh7@u^(%?$#|&J$pvFC-n8&A>utA=n3#)yMkz+qnG3wd zP7xCnF|$9Dif@N~L)Vde3hW8W!UY0BgT2v(wzp;tlLmyk2%N|0jfG$%<;A&IVrOI< z!L)o>j>;dFaqA3pL}b-Je(bB@VJ4%!JeX@3x!i{yIeIso^=n?fDX`3bU=eG7sTc%g%ye8$v8P@yKE^XD=NYxTb zbf!Mk=h|otpqjFaA-vs5YOF-*GwWPc7VbaOW&stlANnCN8iftFMMrUdYNJ_Bnn5Vt zxfz@Ah|+4&P;reZxp;MmEI7C|FOv8NKUm8njF7Wb6Gi7DeODLl&G~}G4be&*Hi0Qw z5}77vL0P+7-B%UL@3n1&JPxW^d@vVwp?u#gVcJqY9#@-3X{ok#UfW3<1fb%FT`|)V~ggq z(3AUoUS-;7)^hCjdT0Kf{i}h)mBg4qhtHHBti=~h^n^OTH5U*XMgDLIR@sre`AaB$ zg)IGBET_4??m@cx&c~bA80O7B8CHR7(LX7%HThkeC*@vi{-pL%e)yXp!B2InafbDF zjPXf1mko3h59{lT6EEbxKO1Z5GF71)WwowO6kY|6tjSVSWdQ}NsK2x{>i|MKZK8%Q zfu&_0D;CO-Jg0#YmyfctyJ!mRJp)e#@O0mYdp|8x;G1%OZQ3Q847YWTyy|%^cpA;m zze0(5p{tMu^lDkpe?HynyO?a1$_LJl2L&mpeKu%8YvgRNr=%2z${%WThHG=vrWY@4 zsA`OP#O&)TetZ>s%h!=+CE15lOOls&nvC~$Qz0Ph7tHiP;O$i|eDwpT{cp>+)0-|; zY$|bB+Gbel>5aRN3>c0x)4U=|X+z+{ zn*_p*EQoquRL+=+p;=lm`d71&1NqBz&_ph)MXu(Nv6&XE7(RsS)^MGj5Q?Fwude-(sq zjJ>aOq!7!EN>@(fK7EE#;i_BGvli`5U;r!YA{JRodLBc6-`n8K+Fjgwb%sX;j=qHQ z7&Tr!)!{HXoO<2BQrV9Sw?JRaLXV8HrsNevvnf>Y-6|{T!pYLl7jp$-nEE z#X!4G4L#K0qG_4Z;Cj6=;b|Be$hi4JvMH!-voxqx^@8cXp`B??eFBz2lLD8RRaRGh zn7kUfy!YV~p(R|p7iC1Rdgt$_24i0cd-S8HpG|`@my70g^y`gu%#Tf_L21-k?sRRZHK&at(*ED0P8iw{7?R$9~OF$Ko;Iu5)ur5<->x!m93Eb zFYpIx60s=Wxxw=`$aS-O&dCO_9?b1yKiPCQmSQb>T)963`*U+Ydj5kI(B(B?HNP8r z*bfSBpSu)w(Z3j7HQoRjUG(+d=IaE~tv}y14zHHs|0UcN52fT8V_<@2ep_ee{QgZG zmgp8iv4V{k;~8@I%M3<#B;2R>Ef(Gg_cQM7%}0s*^)SK6!Ym+~P^58*wnwV1BW@eG z4sZLqsUvBbFsr#8u7S1r4teQ;t)Y@jnn_m5jS$CsW1um!p&PqAcc8!zyiXHVta9QC zY~wCwCF0U%xiQPD_INKtTb;A|Zf29(mu9NI;E zc-e>*1%(LSXB`g}kd`#}O;veb<(sk~RWL|f3ljxCnEZDdNSTDV6#Td({6l&y4IjKF z^}lIUq*ZUqgTPumD)RrCN{M^jhY>E~1pn|KOZ5((%F)G|*ZQ|r4zIbrEiV%42hJV8 z3xS)=!X1+=olbdGJ=yZil?oXLct8FM{(6ikLL3E%=q#O6(H$p~gQu6T8N!plf!96| z&Q3=`L~>U0zZh;z(pGR2^S^{#PrPxTRHD1RQOON&f)Siaf`GLj#UOk&(|@0?zm;Sx ztsGt8=29-MZs5CSf1l1jNFtNt5rFNZxJPvkNu~2}7*9468TWm>nN9TP&^!;J{-h)_ z7WsHH9|F%I`Pb!>KAS3jQWKfGivTVkMJLO-HUGM_a4UQ_%RgL6WZvrW+Z4ujZn;y@ zz9$=oO!7qVTaQAA^BhX&ZxS*|5dj803M=k&2%QrXda`-Q#IoZL6E(g+tN!6CA!CP* zCpWtCujIea)ENl0liwVfj)Nc<9mV%+e@=d`haoZ*`B7+PNjEbXBkv=B+Pi^~L#EO$D$ZqTiD8f<5$eyb54-(=3 zh)6i8i|jp(@OnRrY5B8t|LFXFQVQ895n*P16cEKTrT*~yLH6Z4e*bZ5otpRDri&+A zfNbK1D5@O=sm`fN=WzWyse!za5n%^+6dHPGX#8DyIK>?9qyX}2XvBWVqbP%%D)7$= z=#$WulZlZR<{m#gU7lwqK4WS1Ne$#_P{b17qe$~UOXCl>5b|6WVh;5vVnR<%d+Lnp z$uEmML38}U4vaW8>shm6CzB(Wei3s#NAWE3)a2)z@i{4jTn;;aQS)O@l{rUM`J@K& l00vQ5JBs~;vo!vr%%-k{2_Fq1Mn4QF81S)AQ99zk{{c4yR+0b! literal 58695 zcma&OV~}Oh(k5J8>Mq;vvTfV8ZQE5{wr$(iDciPf+tV}m-if*I+;_h3N1nY;M6TF7 zBc7A_WUgl&IY|&uNFbnJzkq;%`2QLZ5b*!{1OkHidzBVe;-?mu5upVElKVGD>pC88 zzP}E3wRHBgaO?2nzdZ5pL;m-xf&RU>buj(E-s=DK zf%>P9se`_emGS@673tqyT^;o8?2H}$uO&&u^TlmHfPgSSfPiTK^AZ7DTPH`Szw4#- z&21E&^c|dx9f;^@46XDX9itS+ZRYuqx#wG*>5Bs&gxwSQbj8grds#xkl;ikls1%(2 zR-`Tn(#9}E_aQ!zu~_iyc0gXp2I`O?erY?=JK{M`Ew(*RP3vy^0=b2E0^PSZgm(P6 z+U<&w#)I=>0z=IC4 zh4Q;eq94OGttUh7AGWu7m){;^Qk*5F6eTn+Ky$x>9Ntl~n0KDzFmB0lBI6?o!({iX zQt=|-9TPjAmCP!eA{r|^71cIvI(1#UCSzPw(L2>8OG0O_RQeJ{{MG)tLQ*aSX{AMS zP-;|nj+9{J&c9UV5Ww|#OE*Ah6?9WaR?B04N|#`m0G-IqwdN~Z{8)!$@UsK>l9H81 z?z`Z@`dWZEvuABvItgYLk-FA(u-$4mfW@2(Eh(9fe`5?WUda#wQa54 z3dXE&-*@lsrR~U#4NqkGM7Yu4#pfGqAmxmGr&Ep?&MwQ9?Z*twtODbi;vK|nQ~d_N z;T5Gtj_HZKu&oTfqQ~i`K!L||U1U=EfW@FzKSx!_`brOs#}9d(!Cu>cN51(FstP_2dJh>IHldL~vIwjZChS-*KcKk5Gz zyoiecAu;ImgF&DPrY6!68)9CM-S8*T5$damK&KdK4S6yg#i9%YBH>Yuw0f280eAv3 za@9e0+I>F}6&QZE5*T8$5__$L>39+GL+Q(}j71dS!_w%B5BdDS56%xX1~(pKYRjT; zbVy6V@Go&vbd_OzK^&!o{)$xIfnHbMJZMOo``vQfBpg7dzc^+&gfh7_=oxk5n(SO3 zr$pV6O0%ZXyK~yn++5#x`M^HzFb3N>Vb-4J%(TAy#3qjo2RzzD*|8Y} z7fEdoY5x9b3idE~-!45v?HQ$IQWc(c>@OZ>p*o&Om#YU904cMNGuEfV=7=&sEBWEO z0*!=GVSv0>d^i9z7Sg{z#So+GM2TEu7$KXJ6>)Bor8P5J(xrxgx+fTLn1?Jlotz*U z(ekS*a2*ml5ft&R;h3Gc2ndTElB!bdMa>UptgIl{pA+&b+z_Y&aS7SWUlwJf-+PRv z$#v|!SP92+41^ppe}~aariwztUtwKA8BBLa5=?j3@~qHfjxkvID8CD`t5*+4s|u4T zLJ9iEfhO4YuAl$)?VsWcln|?(P=CA|!u}ab3c3fL8ej9fW;K|@3-c@y4I;^8?K!i0 zS(5Cm#i85BGZov}qp+<-5!Fh+KZev3(sA2D_4Z~ZLmB5B$_Yw2aY{kA$zuzggbD{T zE>#yd3ilpjM4F^dmfW#p#*;@RgBg{!_3b6cW?^iYcP!mjj!}pkNi{2da-ZCD2TKKz zH^x^+YgBb=dtg@_(Cy33D|#IZ&8t?w8$E8P0fmX#GIzq~w51uYmFs{aY76e0_~z2M z(o%PNTIipeOIq(H5O>OJ*v8KZE>U@kw5(LkumNrY>Rv7BlW7{_R9v@N63rK)*tu|S zKzq|aNs@81YUVZ5vm>+pc42CDPwQa>oxrsXkRdowWP!w?=M(fn3y6frEV*;WwfUV$s31D!S_;_~E@MEZ>|~wmIr05#z2J+& zBme6rnxfCp&kP@sP)NwG>!#WqzG>KN7VC~Gdg493So%%-P%Rk!<|~-U|L3VASMj9K zk(Pfm1oj~>$A>MFFdAC8M&X0i9-cV7Q($(R5C&nR5RH$T&7M=pCDl`MpAHPOha!4r zQnYz$7B1iLK$>_Ai%kZQaj-9)nH$)tESWUSDGs2|7plF4cq1Oj-U|+l4Ga}>k!efC z*ecEudbliG+%wI8J#qI!s@t%0y9R$MBUFB)4d47VmI`FjtzNd_xit&l1T@drx z&4>Aj<2{1gUW8&EihwT1mZeliwrCN{R|4@w4@@Btov?x5ZVzrs&gF0n4jGSE33ddUnBg_nO4Zw)yB$J-{@a8 z);m%fvX2fvXxogriNb}}A8HxA)1P-oK+Da4C3pofK3>U_6%DsXFpPX}3F8O`uIpLn zdKjq(QxJTJ4xh->(=lxWO#^XAa~<7UxQl8~8=izS!TcPmAiBP5Et7y?qEbFd9Q=%IJ;%Kn$lto-~3`}&`x=AVS+Uo7N*hbUxhqVH_w^sn!74z{Ka#*U6s z=8jIrHpUMBC@@9Jn~GS<$lse*EKuX%3Swl5&3~GiK_$vn8Vjqe{mjhBlH}m4I8qK+ ztU50COh7)d-gXpq-|}T;biGa^e=VjxjjFuoGIA8`2jJ}wNBRcsx24?7lJ7W4ksNPv zA7|gcXT@~7KTID#0|EX#OAXvgaBJ8Jg!7X#kc1^Tvl;I(=~(jtn-(5bhB=~J^w5bw z8^Hifeupm;nwsSDkT{?x?E(DgLC~Nh8HKQGv`~2jMYrz9PwS^8qs3@nz4ZBCP5}%i z=w}jr2*$X-f(zDhu%D8(hWCpix>TQpi{e`-{p^y?x4?9%)^wWc?L}UMcfp~lL|;g) zmtkcXGi9#?cFOQQi_!Z8b;4R%4y{$SN~fkFedDJ&3eBfHg|DRSx09!tjoDHgD510Z z_aJLHdS&7;Dl;X|WBVyl_+d+2_MK07^X1JEi_)v$Z*ny-()VrD6VWx|Un{)gO0*FQ zX{8Ss3JMrV15zXyfCTsVO@hs49m&mN(QMdL3&x@uQqOyh2gnGJYocz0G=?BX7qxA{ zXe0bn4ij^;wfZfnRlIYkWS^usYI@goI9PccI>}Ih*B!%zv6P$DoXsS%?G)|HHevkG z>`b#vtP=Lx$Ee(t??%_+jh(nuc0Q&mCU{E3U z1NqNK!XOE#H2Pybjg0_tYz^bzX`^RR{F2ML^+<8Q{a;t(#&af8@c6K2y2m zP|parK=qf`I`#YxwL=NTP>tMiLR(d|<#gEu=L-c!r&(+CpSMB5ChYW1pUmTVdCWw|!Ao?j&-*~50S`=) z9#Knf7GPA19g%Y7wip@`nj$aJcV|SakXZ*Q2k$_SZlNMx!eY8exF;navr&R)?NO9k z#V&~KLZ0c9m|Mf4Gic}+<=w9YPlY@|Pw*z?70dwOtb<9-(0GOg>{sZaMkZc9DVk0r zKt%g5B1-8xj$Z)>tWK-Gl4{%XF55_Ra3}pSY<@Y&9mw`1jW8|&Zm{BmHt^g=FlE{` z9Lu7fI2v3_0u~apyA;wa|S4NaaG>eHEw&3lNFVd_R9E=Y? zgpVQxc9{drFt2pP#ZiN~(PL%9daP4pWd*5ABZYK{a@e&Vb`TYiLt$1S>KceK36Ehz z;;MI%V;I`#VoSVAgK3I%-c>ViA>nt=5EZ zjr$Jv~$_vg<$q<@CpZ1gdqP_3v^)uaqZ`?RS_>f(pWx3(H;gWpjR?W8L++YPW;)Vw3)~tozdySrB3A2;O<%1F8?Il4G|rO0mEZYHDz!?ke!$^bEiWRC1B%j~ws0+hHS;B8l5Wh)e+Ms7f4M4CbL%Q_*i~cP}5-B(UkE&f7*pW6OtYk5okQCEoN4v|7;(+~~nyViqo5 z(bMGQi$)KN6EmfVHv4pf2zZMJbcAKyYy>jY@>LB5eId|2Vsp{>NMlsee-tmh({;@b z@g;wiv8@a1qrDf-@7$(MR^M^*dKYBewhIDFX%;*8s zR#u?E;DJO;VnTY6IfbO=dQ61V0DisUAs4~t|9`9ZE(jG}ax#-xikDhsO_4^RaK ziZ?9AJQP_{9WuzVk^s_U+3V8gOvVl5(#1>}a|RL>};+uJB%nQM-J>M4~yK)cioytFXtnmOaJZSiE+3g}C`Im~6H z*+-vjI>ng5w>>Y!L(+DwX2gs0!&-BFEaDie4i5ln*NGP$te7$F9iUlJl4`XpkAsPm z0l?GQ17uN^=g~u1*$)S`30xL%!`LW*flwT*#svAtY(kHXFfvA`dj*pDfr0pBZ`!La zWmX$Z@qyv|{nNsRS|+CzN-Pvb>47HEDeUGFhpp5C_NL0Vp~{Wc{bsm_5J!#tuqW@? z)Be zb&Gj&(l*bHQDq7w-b`F9MHEH*{Dh~0`Gn8t`pz}!R+q~4u$T@cVaUu`E^%0f-q*hM z1To6V31UGJN7a-QW5;nhk#C26vmHyjTVZkdV zqYMI9jQY)3oZt=V0L7JZQ=^c2k){Y_lHp&V_LIi*iX^Ih3vZ_K<@Di(hY<&g^f?c$wwF-wX1VLj>ZC4{0#e`XhbL_$a9uXS zKph*4LupSV2TQBCJ4AfOXD8fs2;bAGz-qU4=Qj$^1ZJX z2TtaVdq>OjaWGvv9)agwV)QW9eTZ-xv`us2!yXSARnD5DwX_Vg*@g4w!-zT|5<}-7 zsnllGRQz>k!LwdU`|i&!Bw^W7CTUU3x`Zg8>XgHj=bo!cd<#pI8*pa*1N`gg~I0ace!wzZoJ)oGScm~D_Sc;#wFed zUo;-*0LaWVCC2yqr6IbeW3`hvXyMfAH94qP2|cN``Z%dSuz8HcQ!WT0k38!X34<6l zHtMV%4fH5<6z-lYcK;CTvzzT6-^xSP>~a*8LfbByHyp$|X*#I6HCAi){gCu1nvN%& zvlSbNFJRCc&8>f`$2Qa`fb@w!C11v1KCn)P9<}ei0}g*cl~9A9h=7(}FO!=cVllq3 z7nD)E%gt;&AYdo{Ljb2~Fm5jy{I><%i*GUlU8crR4k(zwQf#nima@xb%O71M#t-4< z(yjX(m^mp_Y;5()naqt2-VibylPS)Oof9uBp$3Gj`>7@gjKwnwRCc>rx%$esn);gI z5B9;~uz57n7Rpm8K^o=_sFPyU?>liHM&8&#O%f)}C5F7gvj#n#TLp@!M~Q?iW~lS}(gy%d&G3p?iBP z(PZQUv07@7!o3~1_l|m5m;Xr)^QK_JaVAY3v1UREC*6>v;AT$BO`nA~KZa1x3kV2F z%iwG7SaaAcT8kalCa^Hg&|eINWmBQA_d8$}B+-Q_@6j_{>a- zwT3CMWG!A}Ef$EvQsjK>o)lJ;q!~#F%wo`k-_mT=+yo%6+`iGe9(XeUl;*-4(`G;M zc@+ep^Xv&<3e7l4wt48iwaLIC1RhSsYrf6>7zXfVD zNNJ1#zM;CjKgfqCabzacX7#oEN{koCnq1-stV+-CMQ=ZX7Fpd*n9`+AEg9=p&q7mTAKXvcbo?$AVvOOp{F>#a;S?joYZl_f}BECS%u&0x!95DR;|QkR9i}`FEAsPb=)I z8nb=4iwjiLRgAF}8WTwAb^eA>QjL4Srqb#n zTwx^-*Z38Uzh@bX$_1tq>m{o8PBX*t3Lqaf$EBqiOU*2NFp{LJX#3}p9{|v{^Hg4f zlhllKI>F+>*%mu6i9V7TT*Wx-zdK z(p8faUOwGOm5mBC%UGA1jO0@IKkG;i&+6Ur8XR2ZuRb$*a}R^-H6eKxcYodlXsF`& z{NkO+;_Yh-Ni@vV9iyzM43Yibn;oC7hPAzC24zs&+RYdY&r`3&&fg2hs62ysV^G`N zHMfBEFo8E3S$0C_m({bL8QCe$B@M{n1dLsaJYIU;(!n*V?0I1OvBB=iYh&`?u8 z&~n-$nbVIhO3mMhCQRlq%XRr1;Hvl=9E_F0sc9!VLnM>@mY~=Cx3K5}wxHKEZF9pC zIdyu1qucM!gEiomw7bW0-RwbX7?o=FE#K0l4`U2KhC8*kMWaEWJyVNZVu_tY2e&4F zb54Lh=Oz>(3?V$!ArXFXh8Cb3i;%KQGCrW$W#;kvx$YA2gofNeu?@nt>Yq8?2uJQp zUTo14hS%&dHF3Uhm~Z1>W)yb%&HoM!3z?%a%dmKT#>}}kKy2B=V3{Nu=bae%V%wU$ zb4%^m?&qn==QeHo`nAs3H}wtiK~!!&i|iBLfazh6!y9F)ToKNyE0B385!zq{p)5vB zvu`R#ULIS|2{3w52c*c$4}Pe>9Fw&U^>Bb_LUWn!xPx3X-uQsv(b1XFvFzn#voq0* z5~o`V_G805QXdgAOwOjoqmZ?uzwBVYSNP0Ie8FL`P0VK1J4CzV@t&%0duHB{;yIL$FZ9 zz#s#%ZG6ya&AwE;0_~^$1K

    Hnj76Oym1QVh(3qRgs)GmgnEt-KxP|nCFY3uezZn zmtR0CZ$Z_-+f07?lu_tr~IC{&U6+QOth>ZgYk4V2FI$B2V3`M`Jk zsr>>lupymPeK129PfpDt9?GA2;I>03Ktz8NxwvTroqu8oaRB&bXT}G=^2UyOW}(4H z;9sG^YwV8K7pC&&viM^X_pfeFoN!cIhrE>OPQ5E<4KKDyPhRV^BGb_^Y6GO6#w}c= zu`0fC-@F4qXQtnB^nPmfI7Uw0bLhY^09TCO+H2(nvg8jdPjMAi4oSX%GP3oeo0`ks z%DoV|waU-Q7_libJCwnnOL9~LoapKqFPpZx?5FygX zsA~*ZR7X=@i{smf?fgxbcY6Y`JvD50P=R;Xv^sANPRp-Hc8n~Wb*gLIaoZJ2Q^CFe z_=G}y&{_NXT|Ob??}$cF7)$oPQMaeN_va1f%>C>V2E01uDU=h~<_fQKjtnl_aho2i zmI|R9jrNdhtl+q*X@}>l08Izz&UJygYkbsqu?4OOclV{GI5h98vfszu2QPiF?{Tvh19u_-C^+NjdAq!tq&Rd`ejXw#` z@U15c$Nmylco)Yj4kctX{L+lz$&CqTT5~}Q>0r-Xe!m5+?du6R&XY|YD5r5C-k*`s zOq-NOg%}RJr5ZWV4)?EO%XzZg&e8qVFQ?40r=8BI-~L%9T7@_{1X@<7RjboXqMzsV z8FiSINMjV*vC^FCv_;`jdJ-{U1<_xjZg4g?ek z4FtsapW_vFGqiGcGHP%?8US~Dfqi8^ZqtHx!}0%dqZFg%nQB)8`mE$~;1)Fb76nFk z@rK#&>2@@)4vO&gb{9&~R8-_{8qz6Rmw`4zeckD(L9xq}{r(fUO0Zh-R(d#x{<0j| z?6xZ2sp3mWnC}40B~g2QinHs1CZqZH&`+x2yBLT8hF7oWNIs_#YK2cyHO6AoGRG|RM>Hyn(ddpXFPAOGh~^0zcat`%&WoEQf9)!@l*3Tt@m>Lb z6$+$c!zsy_=%L9!_;jfd`?VXDd*^Vn%G>n~V9Vr6+_D@#E+dWB#&zAE+6xJeDMr1j zV+Tp~ht!M%^6f?)LBf8U1O4G#CutR07SB>8C&_&;g3TdIR#~e~qRtwd>&)|-ztJJ#4y0|UMjhJZlS8gA zAA260zUh+!$+xMfWKs|Lr23bcy#)JNnY|?WOka&wTS7_u%*N7PrMl1Lp9gxJY%CF? zz4IA@VVxX{knZPlNF+$9)>YIj#+(|$aflt=Wnforgn6`^3T+vaMmbshBjDi&tR(a7 zky~xCa77poRXPPam)@_UCwPdha^X~Aum=c0I@yTyD&Z!3pkA7LKr%Y6g%;~0<`{2& zS7W$AY$Kd}3Tg9CJgx=_gKR59zTMROsos?PU6&ocyCwCs8Qx1R%2#!&5c%~B+APu( z<1EXfahbm{XtOBK%@2a3&!cJ6R^g|2iLIN1)C2|l=;uj%tgSHoq2ojec6_4@6b<8BYG1h-Pm_V6dkRB!{T?jwVIIj&;~b7#%5Ew=0Fx zc(p7D1TT&e=hVt4spli}{J6tJ^}WL>sb`k}&gz+6It`Yz6dZdI53%$TR6!kSK2CfT*Q$`P30 z;$+G$D*C$U(^kkeY!OWn$j@IUu0_a{bZQ=TCbHD1EtmZ0-IBR<_3=tT%cz$>EE!V}pvfn7EMWs^971+XK}~kxSc_ATJJD$?)1Gz^Jq!>Hz#KkdCJ~jb-Y*Xv01_}}=T_V-A1<3O!V9Ezf z%Lnjihb3>=ZV}jSeqNu5AAdVbe|`;|p<%W#-<$s1oDYrB;C({psqV>ENkhadsC{cfEx=teVSB`?FOs+}d#pssxP z(ihudAVu3%%!*vOIWY11fn1M0&W|(|<2lEShz|#%W|wV2qM%#+P9NOy1x8jytHpfU zh;_L^uiL<<$L@~NpRXSrkJgdC>9R=>FmVu3^#C?3H>P{ue=mcv7lBmnfA?mB|L)EF zHv%Nl|D}0Tb~JVnv$ZysvbD8zw)>|5NpW3foe!QHipV9>Zy`|<5?O+rsBr*nZ4OE} zUytv%Rw7>^moSMsSU?@&a9+OdVgzWZnD>QXcUd{dd7vad+=0Hy)4|0A`}rpCx6cu!Ee5AM=iJ?|6=pG^>q(ExotyZP3(2PGhgg6-FkkQHS?nHX(yU0NG;4foCV|&)7 z1YK!bnv%#5n<25|CZ>4r1nK=D39qMzLAja*^#CN(aBbMx${?Iur3t=g2EMK|KwOF?I@W~0y`al&TGqJ zwf#~(?!>@#|JbDjQV9ct%+51l%q|lcY&f{FV&ACRVW*%VY6G5DzTpC!e%=T30mvav zRk$JOTntNoxRv>PDlJG1X=uep&???K00ep|l_#7=YZPuRHYoM46Z$O=ZZuGy_njgC z>P@gd+zKH5SjpWQ!h_r*!ol1s{9DS@sD4}xgFxaw>|av!xrKzg?rGnhZ#uZeU~iod z3-i*Hl@7cge0);y{DCVU(Ni1zg{yE&CxYT7)@zJ%ZZABj-Fh}0au^)*aw`vpmym;( z5|JZ!EACYenKNXH%=Md{my$sI3!8^FgtqkMcUR%w_)EBdP5DZ64aCIR%K99tId6SU ziT8Ef)K%7{XuIpPi}N+&FCm$elE>oKY;3c$x+*mXy?~wt6~?ss$HGqCm=YL2xzVTQ zr>*2_F;7j{5}NUPQ(aY0+h~rOKN|IA28L7^4XjX!L0C^vFB+3R5*1+s@k7;4d#U=5 zXTy8JN^_BCx1a4O3HMa9rf@?Fz>>dq}uvkY7!c?oksgs~xrpCo1{}^PD?w}Ug z3MbfBtRi z$ze~eRSLW^6bDJJeAt^5El{T*i1*v9wX{T7`a2wAVA z%j>3m*g^lc*~GOHFNy?h7>f7mPU*)3J>yPosaGkok}2#?wX5d$9moM~{NTzLznVhX zKa}bFQt#De`atoWzj4Lb@ZCud_T9rA@6VcmvW(+X?oIaH-FDbEg#0Slwf|7f!zUO( z7EUzpBOODL&w~(tNt0z|<9}Filev&4y;SQPp+?kIvJgnpc!^eYmsWz1)^n`LmP&Ui z-Oi1J2&O|$I<^V@g2Z91l3OArSbCkYAD0Tuw-O(INJJ>t%`DfIj}6%zmO+=-L{b!P zLRKvZHBT=^`60YuZon~D$;8UDlb-5l8J=1erf$H(r~ryWFN)+yY@a;=CjeUGNmexR zN)@)xaHmyp$SJcl>9)buKst5_+XomJu34&QMyS zQR(N@C$@%EmfWB8dFN(@Z%xmRma@>QU}!{3=E`wrRCQ~W=Dwb}*CW8KxAJ;v@TAs3 zW}Pq5JPc)(C8Rths1LR}Bgcf6dPOX<#X08^QHkznM-S>6YF(siF;pf~!@)O{KR4q1_c`T9gxSEf`_;a-=bg6=8W zQ&t`BK^gsK-E0Jp{^gW&8F9k?L4<#}Y0icYT2r+Dvg!bnY;lNNCj_3=N=yd9cM9kY zLFg|R0X;NRMY%zD*DbAmFV`(V@IANtz4^_32CH*)XCc$A>P-v49$k@!o$8%Ug>3-- z$#Fpo9J>eUMKg>Cn+T0H!n0Hf#avZX4pp54cv}YcutP+CmKC~a745-zhZp`KNms;J zS3S49WEyS8gCRAY|B~6yDh*cehY52jOSA#MZmk2dzu`_XpBXx9jDf!H3~!`n zaGe=)1VkfIz?*$T3t>-Pwhrw447idZxrsi;ks;(NF>uVl12}zI(N~2Gxi)8yDv-TLgbZ;L&{ax&TBv;m@z6RcbakF^el{!&)<___n#_|XR%jedxzfXG!a2Eyi)4g zYAWkYK{bQzhm|=>4+*SLTG2<#7g-{oB48b05=?PeW;Jo3ebWlo5y5|cl?p8)~PVZqiT^A~w-V*st8kV%%Et1(}x(mE0br-#hyPspVehofF`{gjFXla1lrqXJqQKE9M)8Xe0ZO&s$}Q zBTPjH>N!UU%bRFqaX(O9KMoG$Zy|xt-kCDjz(E*VDaI={%q? zURR{qi>G^wNteX|?&ZfhK-93KZlPXmGMsPd1o?*f_ej~TkoQ#no}~&#{O=>RadgtR zvig@~IZMsm3)vOr`>TGKD&fbRoB*0xhK7|R?Jh-NzkmR}H6lJiAZTIM1#AXE1LOGx zm7j;4b(Lu6d6GwtnsCvImB8%KJD+8z?W{_bDEB$ulcKP*v;c z*Ymsd)aP+t$dAfC-XnbwDx3HXKrB{91~O}OBx)fsb{s-qXkY<@QK7p-q-aaX&F?GS z2};`CqoNJ$<0DuM2!NCbtIpJ9*1a8?PH#bnF#xf~AYOIc4dx1Bw@K=)9bRX;ehYs; z$_=Ro(1!iIM=kZDlHFB>Ef46#rUwLM%)(#oAG(gYp>0tc##V{#aBl!q``!iIe1GBn z+6^G^5)(nr z8h#bm1ZzI450T?!EL)>RWX8VwT1X`2f;dW!{b~S>#$Pa~D6#Hp!;85XzluH%v5325 z730-aW?rY1!EAt;j7d23qfbMEyRZqxP};uID8xmG@mGw~3#2T^B~~14K5?&dP&H@r zL|aXJsEcAAXEXfu2d-!otZTV=if~^EQD*!NkUFQaheV&b-?-zH6JfjKO)aYN=Do*5 zYZ-@m#)5U0c&sUqu_%-Editr5#%Ne&bs)DxOj2_}`f;I_ReEY9U&Cf3rb>A3LK(ZD zid0_-3RfsS*t&g!zw}C_9u(_ze-vc1L59CdBl(IS^yrvsksfvjXfm>(lcol%L3))Q z@ZT;aumO3Q#8R!-)U697NBM@11jQ>lWBPs#?M4_(w=V_73rsiZh8awEm>q1phn1Ks ze@D|zskeome3uilE8-dgG(EojlI(@Yhfm}Xh_AgueHV`SL##I@?VR+bEHH=sh21A_ zhs&pIN7YTLcmJiyf4lZ;`?pN0`8@QbzDpmT`$m0CTrTMiCq%dE&Cd_{-h`I~f8Kps zAuZt4z)}@T>w$9V@iLi=mh({yiCl}}d>JN)z;*G<6&mgl(CYhJHCAPl=PYK2D>*F zy;YK=xS@1JW7i=C)T04(2P#|fowalY=`Y`G8?eRMAKt|ddG9UF^0M5 zW=ZGZ5qb-z@}iS`4RKXvuPIfzUHT)rv<8a|b?bgB3n=ziCiX4m2~CdVBKHWxw2+Hz zLvqoAij9(0moKoo2$`dqS0?5-(?^RXfcsQB6hU2SAgq8wyeasuyFGcK+@An?8ZzVw zW8wwbZB@i=<<4fA7JKPkki6y>>qO3_bW>-uQ*>9g+g7M0U^`RV)YTrGu2Q=2K>fiI zY0dFs>+}xuOZE^efLK2K6&X@>+y10Oqejnnq^NjfXt9JpK4K_E=cl29 z(t2P;kl4AK_Jg9v{1(z)ESpyo_(Z`74D&J1A#J?l5&J^Ad1sm5;Po@s9v7wOs(=_T zkutjt`BaxT09G{-r>yzyKLlM(k`GZl5m+Tgvq=IN|VjtJ*Zu66@#Rw;qdfZqi15A@fr^vz?071F5!T`s>Lx5!TszI%UK|7dDU;rUCwrRcLh!TZZ9$UMfo z@Qzjw>tKS3&-pyWS^p4mMtx`AvwxVc?g?#8aj@jQ#YKDG0aCx{pU+36?ctAiz=f$k z05S(b&VPQgA(Sm`oP&M^eiHvBe&PcTb+j$!!Yx(j3iI5zcQLOn(QqfX5OElbSsQBUw7);5C92onieJyx`p{V!iwXk)+1v zA6vStRZo0hc>m5yz-pkby#9`iG5+qJ{x>6I@qeAK zSBFylj8{FU*0YbFd2FZ6zdt^2p?V;3F~kap`UQgf@}c33+6xP)hK)fmDo@mm=`47* z9S6rnwCSL&aqgZs959!lhEZZp`*>V8ifNmL;cqajMuaJ~t`;jLPB?X~Ylk_Z#Q;%} zV+sAJ=4505-DdnIR=@D_a`Gy#RxtSX+i-zInO@LVDOd*p>M-|X(qRrZ3S(>(=Oj>} z89d75&n?m^j>;SOXM=)vNoum|3YmzxjYx%^AU*V|5v@SjBYtESp^yz?eQ#>5pnCj} zJ_WCw23wGd2AA-iBve8Hq8`%B3K4@9q@a}sf$49IA^IPsX@QK)36mrzqOv?R_n9K@ zw3=^_m#j{gNR0;&+F~wlS(i8IQN8mIvIO)mkx|e)u*y+xDie}%mkZ*m)BQM^$R@-g z1FrP0{8A?EcxtxxxX&J;393ljwwG?2A2?y-1M0-tw$?5ssoEsbPi?sd2!s~TrwPLF zYo-5XYV7AU-c|Vb-v;>pVi^CwX(Rpt<9{Ic?@<9SrNu>F(gwij%?dC9^!Xo90o1-| z&_aPKo%+xyw64e&v<}F^-7sO0Cz-VOF@7**i@v&(Oy4Q8PbV+4&rKwmYyokM z48OZ|^%*mC_Q)RJ31D#b4o4Jzr{~BX4D#swW<31;qCil2qlim;e=9ymJAEXfv-|h3 z)>uqQ5~S+8IgiWW28Fqbq+@ukCLy+k7eGa1i5#G_tAUquw$FjFvQt6~kWa69KXvAj z-knF`5yWMEJvCbTX!K{L)VeNF?(+s?eNjtE5ivg^-#937-l()2nKr#cHShB&Pl^l8 zVYws26D^7nXPlm<_DYU{iDS>6Bq0@QsN%6n>XHVvP<^rDWscC!c+LFrK#)T@$%_0{ zob%f&oaq>1_Z8Ata@Y2K6n?GYg|l8SgUr(}hi4D!@KL~hjRv<}ZZ`tCD^ev=H&^0pP%6q2e+t=Ua`ag8xqWvNnIvCU|6ZA^L5v{DD)!mcQ@n6{=; z#Z)PrAz>*+h-|IV!&J*f@{xb!L7h3{?FEs*ifw5z2U9$&OkYseI68yb=V4xv*VK3- zVxGhtmedujX32y-kC{5ej-Wy#JvB~4oxTb{|1H825_B(A0#?CjUTc=PrGh6jAgK9h zoLAe`+NBdStZE@Y8UH^Rd*|R-|7Ke}wr$(CZQHhO+upHlCp)%n+fH_}S8%^%xqhu%20_1p=x#Dl9ia`c3iM+9Vh5?gyY8M9c$tJ5>}V_sidHN zoMl%rSgSK!7+Y8tQkYq|;Vh`4by2uMsUfnxkk2{S@a>V#d}fv}Yud*>paVi_~T zU!GoYwWbnG%92!Cte(zhZX-i9#KJ;b{$(aZs|{MerP#6||UUx$=y)4XOb zihyKn`_QhJ#~@_peJ*8yD4>I7wQyKkZG%#FTKZfb(@G+9x7-3@hG}+ZC&$7DwbaB$ zC)jLj7yituY&WpOWlG7Z4Tuxzdwo6k!3lgwhh7BYMyB? zO9Q5nvn77~g~c623b`Pe5efNzYD#2Sfmg>aMB5s?4NC|-0pIXy%%`J;+E{(irb!Szc8M8A@!}0zqJLoG4SJ5$~1*yRo0^Z`uObA+= zV?1sYNvzvWbP%AsMzoIo3Cwx~y%i8rHF(BgLS>tH5Ab|1wp$X_3o2_VB(pFxgQ5QQ zk@)Vy95$b%HVf4@ppX(wrv^Jwfrsu+9N_OUm}nD7Ch_7STj66EYsZR#`9k|Tf^@p& ziHwnO$p{TB#R(Q{Os>Un~0!r$JO zLZ&F%SP|%$TuG)mFeOhKr1?S!aa0jTV$2XIeZb_fgO&n{8HTe9s`L&(tKoy?OaS^$ zLHNrgYgq920EI~M>LyU7gK70$7*`nFKD^d>MoEAhsBU0%@*RW@%T(J z?+wVbz=mcN%4#7qlCpl_^Ay7VB%?+uW1WSNnQOj^tALyqTpV zkEN2C;qO_W)MYl^Ow5I;t3;z#iG82F(qe}#QeE;AjA=wM==dB(Gu+ez*5|RVxO4}l zt`o?*B;);-0`vR(#+Q^L4WH_9wklh-S-L-_zd%Q0LZ%|H5=>Z)-x#Z+m%p&6$2ScV zEBneIGo)r0oT)xjze*Q~AIqhB%lOM5Id}^eKwS!?b_;B&TouZsemyL&y`)#FX}ZKp zp)ZnB*^)1P@2bCoe+Z|#KhTBNrT)UN@WIuudw})fwHl)re1|b~E1F=xpH?7L77p>5 zei$aD@KO0<+zo1<&7OuZatNsPq24Whu%0jD_ z$ZZy6MzayYgTJulNEy8D$F%JDYgx|d6{6kpDg#s170<15bM#4tzvrDU$6bvu-hH@6 zgcjq&3aR3k(23$FaUA|iuoy*bO{2F6W0<+ZdsYvXjc?d@ZT8kM!GD}r@qr;TF@0Hb z2Dz-A!HZ$-qJ?F%w6_`t`8xk$f$MNBfjqwvJiVdD+pf7NVFGh?O=qp2vh%UcYvc{rFldib~rkIlo`seU%pO_6hmBWGMcUhsBSWiQYYPMX<-Cjp49@7U==iS57bG zw3T9Nbm`)m9<<4e$U74`t~zRo0JSfi}=GdQXGLLPyW zlT^I}y=t$j{Vx!wN^z8X4l0|@RNrC#)G>bK)7IT7Qop>YdS^NnI3gfP>vtp)pXkr2WSVcAAv8uN>@ z`6)kICvNYU$DA8pnkl4sQopDC6<_M8zGJ^@ANXJL(yd#n1XFj9pH;rld*gwY8om_I zdB55w@FUQ_2k}d%HtQsmUx_7Mzftky&o2X2yDQrgGcehmrDDDtUJj5``AX$gzEbMc zUj2Qzp)Lo>y-O*@HJ|g9$GR2-jgjKfB68J6OlIg;4F2@2?FlW zqj|lO7A2Ts-Kd!SO|r9XLbPt_B~pBpF40xcr0h=a&$bg(cwjp>v%d~Uk-7GUWom?1 z92p+C0~)Og*-N~daT#gQdG{&dPRZso(#{jGeDb1G`N)^nFSB`{2-UQ&!fkPyK`m03 z_Di94`{-(%3nE4}7;4MZ)Pmawf#{}lyTSs5f(r;r1Dp4<;27K=F}Oga^VsUs3*NIn zOsYstpqpRF&rq^9>m50LRORj>=;{CV2&#C$-{M5{oY9biBSoQyXvugVcwyT-19S;pf!`GSNqb4**TI%Y z*zyV)XN3Fdp3RNNr9FU+cV*tt?4L8>D@kJp^rkf_rJ~DPYL}oJngd1^l!4ITQN`0RTT^iq4xMg|S6;d}lznE$Ip^8pW-CHu zP*^!U>Lcd3*shqa)pswq;y<|ISM1g1RG#`|MSPNAsw*XH1IAD(e(Kgqp6aDHgv>fI z!P67$z{#()Pdo3;4dUoy*Xor(O?+YTRPe=g*FfRj*9q9!8p%1l>g3e^rQ_nm{(@4t z?^nMDC2J8@my5q0QyCljCSp_@)No+6bZ*y)lSdrkLFcR6YOHu*vZ-q(C);5$MmM_z z1WT>Gc8g%`Rt~6*!}JhWi0=Rc_z5c8GR9YXW+cdoK~Ea(@wyXf|89HagNuFAO-V7k zUb|9zaCCWH3^Fz(m7$8K$|0ZOP!SNpgP!ql<)!z8w$Z$?9gq2f<~koe3|zD=imLfD z>IV5?SkRZ;7JlOG%z%Tlze$GXr0A}ResyF63ZGZVDLv2k4HWtoqoCaq+Z&GaVKuLA z>@zhNjYYc=sexH?;DTe4&2vnQE}C@UFo&|qcLddvH0FwswdRUc(p*X&IT^Zu>xLpG zn(@C%3ig(l2ZPm#Fc){+0b+%O7nt4zbOt+3@GQVm|1t70=-U(>yo3VY2`FnXFHUyi zwiqf(akt0kEE5_Pa-a*VCS}Pi6?`~P%bvX6UT~r-tUAY%I4XF3^nC+tf3alyL{M`w zv?aVQ#usdwpZmkrfv19O39}tQPQM+oY**a{X?@3Qe>r$+G!>r#?Id&U&m^HU(f= zjVpSi9M||1FyNQA&PO`*94&(qTTMQv3-z`bpCXs-3bX}#Ovqec<>omYhB*VrwxqjY zF3#OXFsj`h#G?F}UAilxTQ|78-edHc-Uc-LHaH*Y(K%R#dVw>_gz}kRD4s#+U&Pq= zps)kMf_t9`GHR7CO4zI8WVj0%qiSqy50N{e_5o#GrvNhMpJf5_sCPrEa%a@ltFnss ziaWh26vEW4fQp}qa4oP(l4xIMpA)~VHD9!lP%;Tm`(HD$jYMM-5Ag>S(gC35J35$%?^gk(r|`4Ewi-W z;f&;B*fO=kC@N=r<-#nGW|yXE;`zb0Y3TJOAkw1a$SQgoTawHZTck+V%T=spmP`^BHihc(jc+S1ObX%6AYQ6LVVc+BfM*P{2s0T2z zVIs*5{ql%#CKAzv0?@S+%||z;`dpfj0Y(VtA51n$j%sG5I%A|h98VU}PkVZFrk1*G zaw75v3(N50lanvr&ND4=7Db;HS4fpi)2vTME7aD2-8N5+kcOXmYCrLE?*5&dWhvB` zbD5)ADuIwwpS*Ms;1qyns(8&tZ*)0*&_lNa`_(phwqkL}h#WdX_ zyKg%+7vP>*&Fus9E4SqIN*Ms`QLB(YOnJ|md%U|X`r#tVN$#q6nEH1|blQ?9e(3|3 z`i#;GUl~v?I6&I6%YvkvmR?*l%&z)Pv8irzVQsWrZSr%aoYuPJa#EjK|4NmiuswK= zlKP2v&;yXv3>LQ$P){aYWrb)5GICwbj;ygw>*amKP;Z{xb^cF}O@IeQ^hB-OjEK{l z>#PNyLuVkeDroL9SK2*ChHmJJSkv@YRn7)E49fy!3tqhq`HtHs_(DK|2Lyv(%9L&f zSy+H}Uk{nE2^5h7zN7;{tP3)$1GK9Xcv^L48Sodg0}ZST@}x607yJo2O*XCfs7*wT@d?G^Q6QQRb!kVn?}iZLUVoyh8M4A^ElaHD*Nn2= zkfCS=(Bg9-Mck6K{ z%ZM59Rs4(j1tSG1B#wS=$kQfXSvw6V>A(IC@>F;5RrCos`N{>Oyg|o*qR2EJ>5Gpe ze~a4CB{mmDXC7C>uS@VL&t%X#&4k<`nDx;Zjmo%?A4fV3KOhBr;VuO!cvM8s2;pG5 zcAs!j?nshFQhNA`G3HMS z?8bfRyy1LwSYktu+I7Hurb-AIU9r|rl5nMd!S&!()6xYNJ1EqJd9BkjgDH@F*! zzjtj4ezywvlkV7X@dG^oOB}T76eK=y!YZB#53LhYsZuP&HdmVL>6kH8&xwa zxv8;t-AE>D5K<{`-({E0O4%fGiLVI8#GfZ0aXR6SfYiPUJKnujMoTI5El<1ZO9w|u zS3lJFx<7XUoUD(@)$pDcs3taMb*(v2yj#G)=Mz-1M1q@Tf4o{s9}Uj9Yo?8refJwV zJ;b+7kf0M}fluzHHHS!Ph8MGJxJNks7C$58^EmlaJcp`5nx+O7?J)4}1!Y>-GHf9o zk}oTyPa>+YC$)(Qm8|MhEWbj?XEq}R=0NFH@F3ymW>&KS!e&k5*05>V@O*~my_Th; zlP05~S5@q+XG>0EuSH!~gZe_@5Dbj}oNIiPJpEOip+3l!gyze@%qOkmjmx=?FWJLF zj?b}f8Vet*yYd16KmM43rVfZo?rz3u|L6Foi*GQe4+{REUv9*}d?%a{%=8|i;I!aT z7Wxm}QJC`?cEt9+$@kSkB!@`TKZz1|yrA1^*7geq zD5Kx-zf|pvWA+8s$egLrb=kY385v2WCGL{y4I15NCz5NMnyXP_^@rsP#LN$%`2+AL zJaUyV<5;B^7f+pLzTN50Z~6KC0WI<|#bMfv+JiP3RTN^2!a7*oi+@v3w*sm5#|7zz zosF*{&;fHBXn2@uguQ1IDsh(oJzH#i4%pk;Qh^T zfQLyOW;E*NqU!Fki*f-T4j(?C$lY2CT{e!uW}8E(evb3!S%>v^NtNy@BTYAD;DkVo zn9ehVGaO7s?PQBP{p%b#orGi6Y&~<;D%XLWdUi}`Nu-(U$wBBTt*|N4##sm2JSuWc)TRoYg57cM*VDGj~ka<=&JF zo8=4>Z8F`wA?AUHtoi$_hHoK!3v?l*P0$g^yipOWlcex4?N2?Ewb1U=lu}0`QICA4 zef61j-^1p}hkA*0_(esa!p%dX6%-1e-eMfQsIp6wRgtE=6=hDe`&jel{y=6x5;78s z?5^{J|t!#x1aS8<3C`v%E%u{*wZwSXr$0Owl5_ zmXh>D>C_SjOCL^CyGZpBpM5`eymt{*rf~9`%F&&o7*S!H%3X)7~QFgn^J>6 zD+yV}u{HN-x9*_$R;a+k?4k*1f)rE~K|QvcC3dlr>!nftB?gE-cfcPMj&9mRl>|Lg zQyCe|&SuZopU0>IfRmcV3^_mhueN5oQ=J+H4%UsSIum4r4!`^DJqZr?1j3BU)Ttzg z6LwM)W&UEMIe*H2T6|{rQ;x9qGbp7ca#-!Egm4|ECNTMN);`>2Q&%|BpOdIJ4l|fp zk!qEhl;n(Y7~R1YNt7FnY10bQZXRna2X`E_D1f*}v1bW^lJorDD0_p2Rkr32n}hY! zCDB(t$)4YOd)97R60gfg3|wrlsVs#4=poh4JS7Ykg$H)vE#B|YFrxU-$Ae^~62e;! zK9mwxK?dV4(|0_sv(zY&mzkf{x@!T8@}Z6Bf)#sfGy#XyRS1{$Bl(6&+db=>uy-@y z$Eq~9fYX$06>PSKAs#|7RqJ3GFb;@(^e`jpo-14%^{|%}&|6h{CD(w@8(bu-m=dVl zoWmYtxTjwKlI!^nwJ}^+ql`&fE#pcj*3I|_Z>#y##e@AvnlSN4po#4N#}WT)V5oNP zkG+h_Yb=fB$)i`e2Fd28kS$;$*_sI;o0Xoj#uVAtsB6CjX&|;Bk}HzQ*hJ!HDQ&qZ z^qf{}c`l^h5sg-i(pEg#_9aW(yTi?#WH=48?2Hfl_X+(SfW)_c48bG5Bf+MDNp>Y#Mpil%{IzCXD&azAq4&1U10=$#ETJzev$)C*S;Pr9papU3OabRQk_toRZ!Ge(4-=Ki8Db?eSBq~ZT#ufL6SKaXZ+9rA~ zQwyTQTI7*NXOhn?^$QOU>Y6PyCFP|pg;wi8VZ5Z$)7+(I_9cy--(;T#c9SO;Hk~|_ z0tEQ)?geu8C(E$>e1wy%f@o;Ar2e#3HZP$I#+9ar9bDa(RUOA+y!oB;NEBQ`VMb@_ zLFj{syU4mN%9GF;zCwNbx@^)jkv$|vFtbtbi7_odG)9s=q(-PtOnIVcwy(FxnEZm&O^y`vwRfhB z7Urcums9SQS6(swAgl?S|WDGUTFQu51yG$8069U zviuZ=@J&7tQ8DZG<(a->RzV+sUrmH$WG+QvZmUJhT*IoR3#3{ugW%XG0s?_ycS6V6 zS)019<_Rl@DN~8K4#w3g_lvRm4mK3&jmI$mwROr0>D`mX+228Dw4r;mvx7df zy~$zP8NjVX?xkGFaV>|BLuXMQ+BN+MMrIB4S6X)p&5l$;6=S8oI9qi&1iQbs?TroDMfCmIeJ}pbVVtVqHhS(zutEy6#UjTk29-+3@W0`KfehW`@np zhhu#)O&g%r)hTj4b$CY41NYp_)7!bYyG;v(rts z^}YDJt2W88H^H;e$LSm3dh=~yi@)mzJtEfW8=4avbeOE&;Oc>-6OHO+MW`XBZ4rO6 zS;nAi**w3Yso4&Ty+8f$uvT?Z)eaLe$KW1I~9YM2zeTIT}C%_G6FPH-s5Wi3r`=I&juGTfl zZ;4qFZV|6V0c&>t!Y>mvGx#1WWL0N5evV=u28K9**dv`}U3tJ$W?>3InXiwyc)SA% zcnH}(zb0@&wmE>J07n#DOs7~lw>5qUY0(JDQszC~KAAM}Bmd-2tGIzUpO@|yGBrJyXGJk3d+7 zJBN0$?Se(rEb0-z2m%CBd;~_4aH04%9UnSc4KP!FDAM5F_EFujJZ!KDR-fn181GX` z8A?8BUYV}D9bCE0eV~M>9SPag%iVCLWOYQJDzC4~B~Ct0{H7x|kOmVcTQ;esvyHJC zi$H0R73Z8+Z!9^3|2tNut#&MVKbm`8?65s)UM8rg6uE(|e^DYqvoc15-f;u8c=>3;Viz*T# zN%!T+Hex0>>_gUKs%+lgY9jo6CnxL6qnQ>C*RseLWRpipqI;AQE7;LUwL`zM%b`Vu z%Sa-+?a#+=)HaD|k2%_(b;pHRF96(c;QyPl6XHL8IqGQKC$M8R=US-c8;hUe?LKo&l!{V)8d&55sUXEu z5uITcO~`ipddh+Nr{7ibp^Wd{bU)^3##<5`lkuqfckxEU*9{pgNpTB2=ku1c-|3dK z|LIQF=ld@I7swq^4|G1VA}BK85&>2p#*P95W`I1FF(8G9vfNJ6MoN$+C^M89u!X=< zJSS%l?Qj>$J%9?0#0&S6#*h*(-9Z$}q*G#hP?cX7cAvM0eiVFhJJ~$`iZM!N5NhDb zi<1u_m#?jzpIaOe7h|Kiap#mHA`L|)ATnPJ7du{^ybuNx@1jA+V1l8ux#{LJ#teM(6=%gZcMq24J$2p z`wcC!qRssmwUv4H6Psw{(YdDNOv$!sq&O1SvIS}fCKZa+`T=Ayt@uZjQqEC{@Uj+| z!;i3W+p~=@fqEEhW@gT^JtCR<`m`i|Htg<TSJ&v`p;55ed zt@a|)70mq;#RP@=%76*iz>fAr7FKd|X8*@?9sWOFf$gbH$XFG zcUNu#=_+ovUd>FW*twO`+NSo*bcea=nbQ_gu^C7iR*dZtYbMkXL5mB@4a3@0wnwH! z(fZKLy+yfQRd%}-!aPC z4GB%OvPHXl(^H(BwVr6u6s=I;`SHQ1um7GPCdP-BjO%OQUH!_UKbEGvHCY}{OL`8FU$GZ;Y$SlS$-0VjK%lCP?U0shcadt4x7lN4%V}wBrLEbiEcK-OHl+pcBNSqN#mftpRj2A4Q z+av@-<#t_Dj_FN^O2~wq(ij1O*+=RVl+6gNV^~CI1UED- zn^zN@UOq8?q58b^4RA>lV}x;jA2OE=SqMYV9P#RsUlI+pp!y*jpwHgp-w3i$V)%?L z>irn1pnRc|P@r|Z0pCeMZ*k$}$`1GVGCT&QtJ`V%Mq!TXoge?8Fjn$bz}NqDn*2ZQ z$p3@F_^(}IVS76>OLNzs`O5!pF=LZ$<&gyuM$HQzHx8ww^FVxnP%Yv2i=m*1ASF~~ zP=!H}b`xl`k0pL5byku2QOS~!_1po!6vQyQL#LQ#rIRr?G5^W?yuNvw-PP{}%m35i$i+I?DJ%RGRcqekT#X~CxOjkV1UQrd&m_bbJ+gsSGbPwKS{F& zU-`QNw!*yq#Co#{)2JvP-6>lY$J$2u+e=r0&kEc#j#jh@4Tp;l*s<28wU%r= zezVPG^r*a?&Fn_(M|A7^xTPD998E-)-A4agNwT?=>FbrHz8w~w?hWBeHVYM()|buJ zvGv4j<%!U_Rh^ZKi~2(h1vk-?o9;`*Zc}m5#o@a1ncp)}rO2SDD9y!nT$_Eb%h`>% zDmssJ8Dl=gDn<-7Ug$~nTaRzd?CJh;?}nCco$7Pz<#J8;YL40#VFbAG|4nA$co;l^byBOT2Ki@gAO!{xU7-TY|rujdYTaWV(Rr{Jwu?(_TA zDR1|~ExJBfJ?MAReMF47u!oEw>JHVREmROknZUs2>yaboEyVs$Pg1f6vs06gCQp$b z?##4PWI#BxjCAVl>46V_dm4?uw=Y@h#}ER4|ACU{lddiweg`vq>gmB25`XuhNai1- zjt{?&%;TRFE+2Y_Gn;p^&&|bU44M=`9!Mc%NbHv|2E4!2+dUL z>6be$Kh|Duz}+)(R7WXsh!m`+#t^Its($x`pqDaN-^E z?*a=0Ck^rZBLQV~jY-SBliN&7%-y3s@FB;X)z(t&D=~@U0vT%xfcu`Lix=W#WVE{{ z2=C~L$>`~@JCIg8RAyk= zYG`(@w4H95n0@Fqv16~nlDU!+QZw&#w@K)hv!V>zA!ZOL$1Iykd&Su3rEln@(gxO| zxWc++T-rQEIL+j7i`TeatMfp4z7Ir31(TE4+_Ds@M|-+cwQg(z>s=S}gsSz{X*Wm+ ziKJWgOd`5^o|5a#i%?Gvw~8e?Rpi7C>nQ5dvPHVTO$PI^mnJ*7?gd3RD{|c_a>WrXT#Es3d}(k z$wpmA#$Q^zFclx{-GUL_M$i0&mRQMd4J#xq-5es)yD{kYCP1s!An(~K5JDRkv6DUSKgo^s@lVM5|V4mWjNZp zsuw^##l%rbRDKglQyj?YT!nk$lNUzh%kH705HWhiMuv(5a<~yoRDM&oCqm+1#S~|8 zA$g2Xr=}p_FX%Eaq{tUO9i*Q1i!>$+1JYZCL}flWRvF0y1=#D#y-JQTwx6uP-(bC} z_uP7)c;Xd`C6k#JVW?#Id7-|`uW+hN0>OM=C2Ta^4?G zr;EvxJ{%l|8D-heRYRM%f*LBC)krHZJ@%&CL0)FADWh14&7KV<9km6gE=o9(7keg~^rIQtthK^_8%Jk&aZLY_bc6SbY>IcwDK9{sV*t1GfKwf8aCo8t za)yALEi^-WXb!k6n>W-62Z^n8hO|eRYr&uZiW5d_URi??nl*aGu?ioQ+9RF9u8kwD z6UZ6HVd(G%l9>y7E)uyn?gAJMKeki0@tG*jdcE-}K?8(D-&n=Ld1i=A1AI<1z>u5p=B z<1}|q3@2jNxW-}Q4z~s|j&^Qc;nXIdS3K8caP_07#ig} z#KAD&ue2jXc&K#Q`Hy#x+LeT4HHUCzi1e?*3w{tK+5Tij(#2l2%p#YGI-b~{5{aS8 z!jABC*n6y~W|h;P!kn(a4$Ri2G118!?0WHDNn((QDJP^I{{wPf<^efQWW?zS>VS?X zfIUgCS{7oV$|7z2hJBt+pp1CPx4L{B_yC3oWdE)d)20WG6m5qknl}8@;kjPJE@!xP zV(Nkv^-Vz>DuwBXmKT(z>57*D<$u=Blt)IS-RK0j89omD{5Ya*ULWkoO)qeM_*)jF zIn87l{kXPp=}4ufM1h7t(lAL?-kEq>_DE-in8-!@+>E1+gCV9Fq)5V3SY?**;AKq0 zIpQ(1u*3MVh#tHRu5E5=B{W-QOI34plm`#uH(mk*;9&Re%?|v-=fvb;?qvVL@gc|l z8^L?2_0ZrVFS-stRY(E>UiQeG_sMrw5UiO znGFLOP-GO{JtBM@!)Q37k3G_p&JhdwPwtJS6@R4_($Ut^b!8HP{52-tkue8MG=Zwr z7u6WaFranJq4oNadY)>_6d~?pKVxg$2Uz`zZPnZVHOh-;M|H7qbV0OF8}z;ZPoI+| z(`e}bn6u*kJpRLC>OZ}gX#eHCMEk#d8y$XzSU;QZ|An$pQ%uZC$=Ki!h@&m8$5(xCtGaY3X1FsU?l5w^Fr{Q-?+EbUBxx+b?D z80o*@qg0juG;aZhj=tO=YHjfo=1+-NqLME~Kw7Y1A*?}M7#cOyT(vd$1tVPKKd@U! z&oV!RzZcK6gPWj`*8FIAy2I&x``h_sXPe*O{|ih(Y+V3|o68MWq~2Iy^iQ8RqK76f zC$1+hXqd^jsz`U{+EFo^VQNrLZt#R`qE*>2-Ip&(@6FmtAngx@+YnG}b5B9Y)^wg#oc z24KlT2s!H_4ZR^1_nDX#UH4(UTgl603&Q3g{G4!?6Sl9Om=Sy|8CjWO>d@e9?Q%s- z-OS3*W_H7*LW|Ne{b+^#LqQ}UKDmiZDma@no2!ydO^jcm>+z379K%=Ifs{20mT|xh zP$e7P=?N(tW4PMHJOQ`a8?n}>^&@<`1Rgo`aRevPp^1n7ibeS6sc8^GPe>c&{Kc+R z^2_F~K=HVI45Pf|<3)^;I{?H}vU7-QK3L1nHpcn3!1_)<$V;e0d_b8^d1T==rVpky zZTn~UvKrjdr11k}UO@o>aR2wn{jX5`KQQM1J1A?^wAFvi&A#NA#`_qKksu`sQ0tdM ziif17TO<{wDq_Q;OM}+1xMji^5X=syK=$QdZnS#dwe$;JYC7JozV8KpwfV}?As|^! zFlln0UitprIpuzLd$`<{_XoUV>rrHgc{cUQH-Px#(_Ul%=#ENrfJe@MRP_$E@FLMa zI`(J)Imw$o427@Oc^3(U&vz}<3Lfmy7diVpJJJ@gA>e;q-&gj zcGcBC_luF%_;**EB?o--G?AkaruJ%-b*8aX$4E+-?V@RWMnjHJ;hx27Vd7l0nUUY( z6OQb&8g8cvN3LZ%^xvIav*X|Epqm@yrTZk9U{GSZXAUJt8Lh(%7?Eaf&AzmXOVvU| zmz<@l1oMe#^POR38KT6q3@c`{%eYNu4ccurv`q?b5DzLxENjSfYOJHAI$MbSNgB*D zJsP>i*BgrFlIn?x&DH9x~UbPBtMFj{_vJ#CaAF>1$oE&k`EF&L@HCa@mN>Q7~!RU>7 zW%fv84aCKSgBacmuvg}r@)YKqO$U{D5|!`vG-Gp%An}raz2gESWm0Exhux4C)zE}} z_@kn z3t}bvm?L+@@az@<*jG>(Xopq&c*;^mttlJ!mv;5k6o%Ac<_`o`4G3qzzo(GO{!&F8 zW+~bF?S;7gO1dQ@>gwZ?iIHjE#^@;Ix!Z`R6{RYLlGB&v4A)ha(2hc`RGV-8`LcvSf+Y@lhT%(Z7$tWEF;cZs2{B|9k#&C}sPyr; zd-g~${TqY7E$9X+h4_(yMxQ%q;tm(h(lKzK)2FQ%k#b2}aMy+a=LHYgk?1|1VQ=&e z9)olOA5H}UD{%nu+!3^HsrBoX^D9Iy0pw!xNGXB6bPSpKDAaun{!fT~Z~`xp&Ii~k zdac?&*lkM+k_&+4oc6=KJ6RwIkB|st@DiQ!4`sI;@40>%zAG^!oG2@ z@eBM$2PJ@F&_3_}oc8A*7mp-0bWng^he9UYX#Ph*JL+<>y+moP^xvQF!MD_)h@b}c2GVX8Ez`x!kjAIV>y9h;2EgwMhDc~tn<2~`lf9j8-Q~yL zM=!Ahm|3JL3?@Tt(OuDDfljlbbN@nIgn#k+7VC+Ko;@iKi>~ovA)(M6rz5KP(yiH| z#iwJqOB7VmFZ#6qI~93C`&qTxT(*Q@om-Xb%ntm_?E;|58Ipd1F!r>^vEjy}*M^E(WslbfLE z<+71#sY~m$gZvoRX@=^FY}X?5qoU|Vg8(o`Om5RM6I(baU^6HmB<+n9rBl@N$CmP41^s?s1ey}wu3r3 z4~1dkyi%kA#*pLQy0phlXa-u(oK2Dwzhuex$YZv=*t*Tg5=n~H=}fJA!p2L78y3D2 zimkqC1gTU(0q||k9QM#><$b-Ilw#Ut2>JF=T^qN34^qcBEd={! zB)rxUbM2IwvMo?S;Id^aglw}-t9et}@TP;!QlFoqqcs(-HfNt9VqGFJ4*Ko*Kk#*B zGpJ>tA9(=t|4#M!kBaf%{$Kfj3-uf|ZFgiU`Bo>%k_OuAp~vnE^_Tg8*% z*?)4JdzyMTzvNDy{r$c``zBw=Vr)6c4}CBIv#mw()3h7`?V-;LF?J&N5a>kjpy;9n zQyXvuu`n?+W84QV=(i`JEJY=}Ak+u4>!Lyt2P!$nBl}T=^|pG*z@)_l!)OKB{tIV&&E@hj=OIhSBHgPV~X=R3NrTMh?VzDm?1yW^IJ&zzAn2{8rE~MRX5EE)a(-T&oE)1J4pGXBYi+nexX-?5! z{EZ4Ju=Y8MQ87=uNc2t^7@X)?85KeSoc`?BmCD;Uv_cwQaLyc}vvnJKHV zuK)H_d)xhGKB!_pRXv{$XgfZ_(8G%N3o$ZI#_ zixQj~so0*m^iuA!bT>&8R@>b%#B~zbIlwt4Ba0v&>B(`*Z;~?6!>-aQ zal+Qt4^dCcjZZMd4b4Khg~(GP#8$3BeB8j!-6l?*##)H?J$PeUy)cA_I26#0aggao zaM5PweS_Sb@{OZ@Uw*(!DNV)KTQU+BTRi?AUAv0Vowth`7mr9)ZVC+TI?@; zWGL&zydnsuE3+D7#U~P%PrxpD3nTc9#mm621iX*?ZMS_Q#n9SzOJ~Hg@`rX{d?qJ; zt}`76!H)MX#=VKifJZP$3<8@}0-llthFpq3FV;(UP$-k63MkHHq~J&}d?C<+c~*Zk z<#G&>AD7EoiAVO38TO2TOBKN>6N|JS*{+`}V-)T0j(bAzGlEUWEvWLrMOIItYexh) z?he>SJk*#bywgDF6+*&%>n%0`-3tOY72+n&Q1NJ`A-bX*2tJV(@;%b6&RxMcUd7+# z@UzOmc9DolSHc-D$5(GouinaE%&uOVMyD&CTdKaEB{Qap4_wU7_=23CULKQ;jmZuV;+Y$(`#Gh0@}s7-!qk-^&#IG>7B{yft?UoA)H5 z|B0u3Tu0TF{AB0jpT|E&RsYB$3WiQU^5p*|f)^Si_#^j+Ao^|5(gNjn+!0|NtXDt* z5fwxpajl@e0FrdEuj2s#Pg>gUvJdko9RBwEe_4@?aEM?SiA2nvm^tsLML{-AvBWM7 z_bm7%tu*MaJkUWd#?GWVrqaQ0>B%Azkxj+Yidvc$XdG1{@$U~uF|1oovneldx`h;9 zB1>H;;n1_5(h`2ECl?bu-sSY@d!QTa`3DrNj_F@vUIdW5{R7$|K{fN11_l7={h7@D z4}I;wCCq>QR6(;JbVbb4$=OBO)#zVu|0iK~SnW~{SrOq&j*_>YRzU&bHUhPPwiy($ zK0qin8U;#F@@}_P_flw`bW_v^G;ct?Pb65%=%egDBgS#YF3?E36$9xzdvYqjAZoK#hcjctJu~MF^S*$q3`o2;!L|jPnM1x*Q~qF%BH(5UDFYglsJwO zEdEuB7NihnTXK6$)F~``nmSQNFP7x7hE{WuOjTAhEjGw#XxvL@S;aZYuyu9)!yZ~X zo35D6Cwb8`shRXCCR;xlR`n`cs4aie!SSM`0)x3ykwM*k zK~w^4x2u#=jEEi`3Q9AU!wE)Zpn#)0!*~)(T^SEjIJveav(d1$RaSMC0|}<)?}nSG zRC2xEBN_YAsuKyl_3yDt%W^F`J-TyeGrcfboC_0Ta=KcW_?~RLb>xbqIVI6`%iWz; zM8Kq9QzwO8w!TntqcB;gNuV$gd+N|(4?6A9GEzYs z5f4(*N5}&ObeYA~I28r;?pKUj4N6}iloE=ok%1|X()Ahdwir?xf6QJfY7owe>pPj)Me*}c^%W-pP6`dnX1&6 z`b#*_P0PeM+1FR)t)Rnr22f!@UFBW!TxgjV)u0%_C~gIbb_D3aPhZ~Wmex0)Lj`VoZKjoW)dUoKY6*| z0|V)|XyjiKgZ}s5(SN?te*muif87vD_(wYOiOjOKNI4L*aK||2$~;s25HS#iY6r=)WW8a^dkd0Y|pPc1-9jmy&wqoCbL84`C94At6$lm_o!8m*did^?o$m?ozIp{RmZ*M%YMX_i$KYkz_Q)QK?Fdm)REqf*f=@>C-SnW{Lb;yYfk&2nAC~b}&B@@^fY7g;n(FVh_hy zW}ifIO9T7nSBHBQP5%-&GF8@A-!%wJAjDn{gAg=lV6IJv!|-QEXT+O>3yoZNCSD3V zG$B?5Xl20xQT?c%cCh?mParFHBsMGB=_5hl#!$W@JHM-vKkiwYqr8kZJ06n%w|-bS zE?p&12hR2B+YB$0GQd;40fJd6#37-qd1}xc1mNCeC%PDxb zlK=X|WE*qn2fROb4{oXtJZSyjOFleI3i8RBZ?2u?EEL1W-~L%7<`H6Vp0;cz5vv`7jlTXf-7XGwp}3|Xl6tNaII3GC z9y1w*@jFLl2iFA!<5AQ~e@S|uK4WL9<$R^??V^aM?Bgy=#|wl$D2P$o;06>{f)P+X z91};NrzVV+)b}k2#rYLF0X0-A+eRul=opDju)g0+vd79B%i!Y}*&a^L$_|C&jQN^j z9q#4<(4)3qNst^+ZYpyVF2hP;DN|OMxM9w(+)%kFQRcYVI zO-frej9x6a%-D%Xuwedcw9#3VSVkOjNF!BYRoY1KD3wFJ%?ML*3QwcarMK)@v`o%s z$w=NLrO>og`nRJpZZ(%~*hNJU#Y~k;_Ci3~gc=4UQO!Ydje^?=W^DgCKyO;Zz4LgQ zKtm($MdY;UZ((U_g5*pMY+dYGyyT1ERkaj`U#S-2yyJ47wMonCpV+2rI8zPNHDfo& zc59dFz*2#^A-R?P6Np}jhDLi4&vP%$NW#8J>=CLj1mlf$XzmQezH*F1jNOiPgXl2j zzD07AKLT*h$CA*OsOba2etPLU%|p?=XhplXo?vOu@q0{QBo++)@6U?YKv_)GFK(^Y zm&uFBbrQyzJm;c49O00PIt;|{&ei%VSS%Y3m3#~L#(3%Gso^a4#9AaB$w@vnAvdr6 z%!2#)YS0HFt%o)q6~BelT;?%oUjX%9qQCn#-~+TM(a^s%Y>&aBkL(UY{+?a9@&Q+a;t%c_6u^6_r@>MEAN9ir5q=Yo|R8z4lKYd1sv^LyTozFn$KqaJ>? zoH&+`AX>E03Gv=71+NZK2>!-NasKeCfMp;@5rZ z*m<}q2!$AgKUwWRXTVHs!E>`FcMT|fzJo30W551|6RoE#Q0WPD$fdA>IRD-C=ae&$=Fuzc6q1CNF>b3z_c<9!;))OViz@ zP58XOt`WOQS)r@tD0IiEIo4Umc(5f%J1p{y4F(1&3AzeAP%V)e#}>2%8W9~x^l}S4 zUOc9^;@m{eUDGL={35TN0+kQbN$X~)P>~L?3FD>s;=PIq9f{Xsl)b7D@8JW{!WVi=s?aqGVKrSJB zO-V&R>_|3@u=MEV1AF%!V*;mZS=ZK9u5OVbETOE$9JhOs!YRxgwRS9XMQ0TArkAi< zu1EC{6!O{djvwxWk_cF`2JgB zE{oo?Cyjy5@Et}<6+>vsYWY3T7S-EcO?8lrm&3!318GR}f~VZMy+(GQ#X9yLEXnnX z7)UaEJSIHQtj5?O(ZJQ{0W{^JrD=EqH_h`gxh^HS!~)?S)s<7ox3eeb7lS!XiKNiWDj5!S1ZVr8m*Vm(LX=PFO>N%y7l+73j-eS1>v0g}5&G zp?qu*PR0C>)@9!mP#acrxNj`*gh}21yrvqyhpQQK)U6|hk1wt3`@h^0-$GQCE z^f#SJiU zb@27$QZ^SVuNSI7qoRcwiH6H(ax|Xx!@g__4i%NN5wu0;mM`CSTZjJw96htSu%C7? z#pPQ9o4xEOJ#DT#KRu9mzu!GH0jb{vhP$nkD}v`n1`tnnNls#^_AN-c~PD;MVeGMBhLT0Ce2O2nwYOlg39xtI24v>pzQ zanl2Vr$77%weA<>>iVZQ&*K9_hfmv=tXiu#PVzNA;M@2}l&vaQsh84GX_+hrIfZC= z0Se*ilv-%zoXRHyvAQW9nOI2C$%DlFH1%zP-4r8bEfHjB3;8{WH`gOYt zg+fX)HIleuMKewYtjg+cSVRUIxAD9xCn+MT zs`DA7)Wx;B`ycL8Q&dR8+8mfhK;a^Rw9 zh9tC~qa>%5T{^8THrj^VEl5Do4j4h@nkrBG6+k8CDD~KB=57m@BL-)vXGkKIuVO9v z7t_L5rpY^0y=uu5iNw0v&Ca-zWk>v;fLJ=+SaV&V#C-o^}8 zp&Xp$v?~ccnfR=&5Df)32^d6QJLg*iuF#s|0M4zJF@Hza1p`q|f}~K)q;HC*I1_9t zQ&1jr9-kdUi8)DGxiwdqU|rPxYWDQPWY&SI&Rxkhxobp~C=Y*`d?HD4JW?WjU7dBPeuIE`ABLq95b#lfKS52IB^6KoHmm60$R}TESplQt59#mboJj+Na!P)V{ic@$yQ-&Z za^JU0T+n0Lf2VdusoNr0?g~1DMsY)zdY-63yH!Ii#aWe|;0TO>L7#YlaDrH}xvYXn zh-NYa>O>f_NTTBG=|k0qWH+X?d5@+INsQ}WcI_3z1Z4-%Gj#_{P$0A~cAye`?j0cW z8)hd(V}7rattLUSMvgZ4g96P7n` z^{55A&&29;-P992{yhkGWa3v_Z6iB4a&~NmL)IpC&dsSwe$9jS(4RVJGt=Y!b-O~1 zSCl@wlaba_cA*yt(QvulMcLUuK z>(ys_!{vqKy{%%~d#4ibQ5$yKn6|4Ky0_ngH>x-}h3pHzRt;iqs}KzajS!i!Pqs8c zCP%xI*d=F=6za_0g`{ZO^mAwRk0iwkzKB7D)SaLR0h|ovGF2w9C9g8;f#EtDN*vBP9yl;n=;B2a7#E8(%Bw()z(M$_pu zQ+9uFnlJ!5&$kk^S_+kJ>r9y8MFPpSf9;o8v;ZxsMA!p>eaAIwt5xNiQ|2_ydGkbi zkggG;Xp&I7C8R{>ten^j@MsN#V5JPs1Ezc!74->Nh0a}U){OK@j=OIoY}C7IYYd8-V9 zQ6s?v=Y7(?Y$7=P#Wwub-*0DLqli?I%kT-D^jqK?c2~HEx<2(poRWAUoC}!~6$1=I z*M(IfPmdID8i+5l@=1(+`?i`G_ew=1Y!gF?tFbdgtW2etKLOFoNozkH(i!Qa7(h^| zF`9!VeqQQwM+yO6J`;oWUWq@9l6hP~FiG8-{Pj*T`XI3~s@FfjW2Tl(llpa901$&y`F}K1uZuHEo;=mr+_8d(o z2Be#yWHEN@euC$=VUSB+3A}khJdF$)0r#<5(f3n`kx>ZT8ifaKyX*OhffeHH1?6OM z*-19$j5tMNYQoB)>cGpz@11>J%q4KW`GLNj?uB>LcNg$0G@}XN#Tqf2F5@jv<`|~p zqB^l!%v!g{R_+0GX5z0>3Q~O``%T$NFc==dsPsTj-;{b$XUS0TGoJs2BUA*H;4S?w z|Nigt|F@9hf7QLSo}JPEK#CPgYgTjrdCSChx0yJeRdbXipF(OwV)ZvghYba)5NZxS zm=L8k_7Lb?f8`=vpv(@m%gzsCs9^E$D5Jn+sf}1lep*zz&5V?~qi_@B?-$Vd1ti(rCi*I0}c}slKv@H_+g?#yarVzpYZN zIk21Bz9Z#WOF`JG&TC&C%a*3*`)GJx9I!U8+!#J4}@5rm8*jK%Xg2VLjP-a;H zFydWO;nxOZ&|{yOW;ta$ZU^6*4vFP)idD6M*M0+9buB#hK4z%YTGBdSva?Pvxim2` zF-?QVGuRQ2-1eYzd1Y%}w^`t1S7|{{8=Es#ApC0<;pc$|NJ)IU%WVK+4gnTWA7-t1 z0K{DCESXb}!y_tzrycr^%%|G4T4)`$BC8+qm|n1lS?CO=`V`1T#ykY#5g5$dc$lGt zqGHyw-*Av%C;33nEiU(rU?w^3F46!dEz#cHd3IF<(XCq)>JG?Bi)4v26MQr1A-g5RqhFoPy%^TD3sa|D^9aS>>_2-X2i#? ztVp@ZkyMB;Uo#9s!R!@G#CCaFVaxx*8YYu$kGFk4g3|9t!1nKqOaDBAe;w!(6#w)0 z?{&F2BgctT1=Z;TvjOGL_!}Vlt=kaLA7#W`mv1h%hUg983!wA*K@_r6_cd6o z6LHiCE6qwlt2H&|Ica~%b9C?Z@$dreBNR_!NKcfL)%8kGr7!IVq|^&6PKYK%EhcKu z6+uR*%EOw=rF6Q42Mx|a> z$2XrM*NV2x9ci6|X^eh1UAbJ9Ky!#*Q5w7)#o#%}d!#-^k8To=n8{UU*LmFsS-wRj zi6-p76V6g?If3S&Bj~GW&QI_WtyPY0@u3hjKtqf9`8S!wn{@P&Tc8uu8cf)YmrX7+ zrC+O3V{9}JG6ihA&^2Q7@)Kq)j(Y_oTzsoBUYQDG!}`Ame`bbcr>J-6E%gaBPEDCU zflX#1-)Ih^HJV*lew*N_SdG-4!b2}G8%U&9_V0~Qt?ZS z@H3L&5ybV8X}A@KQADl93H`}0qkNm!jGHkCJUM%r8`mP1nV?Oo%^l;yDnU6IJtbuY z`X2Sf8|r00mB_f)Q0;S{FqS1Yq?otd-BVbw`#@SDd5}n5X4lqdDi1*vtVv8-Zi10q zexCj0eyngrp`UxjEOrdzUt`?%jRlj7zSU-V-%R?y+_w7P7f1ge%t1ozmN+&)%3xQW zT3u@)))(_a<6`lTJd`DIYw>(pkb=PMKvCNEG~zza+LVNqkY^}QoGMVdS0K;gS*A3f z;6Ua!^sSV-try(M^pB6D9dsX}c>$Da#NHucp9vr(fg4pbBR*uPhYq+N>q1X4RSOCl znIQj4=A+y+8{?LQ$3L@(!Yy~~Cu4Sx72*%@dW>eP%Br7=uaynV6Mqa-49A9) z|L&5r=4K5SClwc`!2J|>(#n$4y1>lmR~2Om8q6HkcpK>d(Fk!T^NO?hM4Fc+(5J{` z&K|vrBz;;zWlNO%=a~JkMxMiZa%wYz#G901lw#+2SUaMMHrebb&|1L8tKoGJK*QhJ zU9|WkDy^-4F6U&VYSc3ScHDk@kV^0801#I|-pSK%az5=DwI}gMm)@s2O+-ESTk?QY z;y9gyucaXO(Cc+cd{B>2)euMHFT71$a6DssWU>>oLw4E-7>FC-YgZH1QAbRwmdahD zO4KAeuA^0q&yWS|zLTx%(P4VOqZv-^BO`0OFAXdBNt9>LAXmPALi3b|gt{b?e-$z0 z4n7H$eg6y_zs(c>*4FT!kN*$H`43~1p!g;IZ8-mYbUPTejaLW#BZnAPFES?ApM{TQ zE*TC%O8)apqcX|PrNjIZE-z{q`I(LwIE0kf=PLjExEX>)oIu><<@lt>-Ng9i$Lrk( znGXl|i4dP;Mt^-IbEp7K0e#*c7By@gCo@VQIW$93ujLL`)lMbA9R?C_5u~7^KopaAMj#6&>n-SOWlup_@{4 zcJ?w_!9JKPM=&Bd#IQ37F*x39y!azm$;~IRlkm>bHdABcNwW-TdDKD$pkD{j6A8d* z{vP~|<}bj_Oz#83K$ieRtsA4a@4a5cRjJ}A01{PgxXn3;fx)5ElMEPwDX_mW9)9oB z*;scve~v#HHqUj3KdC$tdV3&0)Whkp-=hKKz{SzD7g0@N!wyv;ZAime7AjB7&)!)5 zp_iVblaf)%agwJqOG2e7WTCM1&khq`{b>fN4n8hOJbvO?Y;60>LIwagLXWC@@0RSR zo%lPo1cUU=g$ahJ8D=;`v~ORUSl(1-&a@yTAC5Y8E892@{P@MM=GXUGpBSXSbSs!N z;L~0D_s7{+^F6c!WW+^yz5~o7eWtsOE}8{hKaFlHgnyBeUJ8Zz2$k7Lrh?NuMU|No zVvsq@57)8zin;&ckR1;*Z%(xH2lBw z`x%N;|H1En8au588bPDxP^$kfpO!bIzz>K=5Jiq9Rg(NGde0g!rKagLa+&yC)jg7y zq}~2IH)N*FJC31qrIH-2;%3^F?=bDD^U2Y;%ftN(v71oY;od+vh!!2z^}GHR$43rg z0In@ki}TglIsMU^O1(SiLK#oiuyw zB>-@z?&uW`ILoPupw0_cs?C|2YoX&87~us+ny%eo{A!3M<-7O7mHUBCgA~{yR!Dc^ zb= z8}s4Ly!GdxEQj7HHr<}iu@%Lu+-bV>EZ6MnB~{v7U59;q<9$h}&0WT;SKRpf2IId ztAjig0@{@!ab z{yVt$e@uJ{3R~8*vfrL03KVF2pS5`oR75rm?1c`@a8e{G$zfx^mA*~d>1x`8#dRm) zFESmEnSSsupfB>h7MipTeE!t>BayDVjH~pu&(FI%bRUpZ*H615?2(_6vNmYwbc^KX4HqSi!&mY9$w zpf%C6vy@O30&3N5#0s_!jDk|6qjb-7wE3YT3DA7q3D`Q&Y*y>XbgE7=g#rPx1hnf8 zTWd{IC!Iysq*vZup5VGrO)UM<3)6raR`rOwk(!ikf3XPp!n|gz0hS*P=VDXAyMW(s zL??-`&IusEuOMrz>m(A1W5Q~>9xJwCExAcMkOBD` zD5BJSadd{0u}%z4r!9qA`FW4;Ka_Qk>FcHxiucGw4L9qhtoge|ag8jbr`7LHSbVQz z6|xUo*^LV1SLxS>?D`m=g{8IC&1YF$e}VRGD#ZOc_15QW%J@FbEj8tE-nGxo4?X02 z@|q#k*G4xMW>q84Xc09pRj@>Hz8t^fMm3n&G;Al6KU*;=W`7Q{$^|=bnZiJ7?(s)@ zB`vW>#zJ{}!8=*|?p(~fcXSanO^j8+q7V!q16*ic!HLRdz0TzNI6}m+=OKd2b8KX< zAcDTj*%~vQlcO+%@H01gjv-1zZaOXVoM*t-+KXTR#NoTf-#{dQAm?GqK6q8Ta zu3xW?t=NE$EfYa#=0HofLn5~c#m-U#Ct_r6~X-pg6k*F zYIP7De52BBwcAnK?O(j?YEs1;q60!-!hTuKzw3T;XcA_w5HvU;tO~}byLA^cggu8i z-IP@pxFjTy&ie28m}j66dm@g78xK7aG{QSR^bAcY+W*xWu;G~I08sf(GK4>K-cbfJ z-%v9DGR77He<291M~=fg>>9&NFQlboP)pC6fT;{>_!lM`A&&HWIMd)Y6e@IL;nvRdBE*Tn({&3{-XJ9helJa{G51Ck}-_Y=5C|fEo z)7fZlsHxN&SY&ZLTdYuBBZnwIh0#VTzmyK>U0|r&SXb&GP0m)1dGV8z(^x6s5yQ-z zEyniK${#U@Y7p@Yxx}E+jA?1@{=|e6UM;iyai=0=aItVvqieogZUq@sio2#9NLW~L z{w@^H!HEGU;>;T0lu{Ad20Hr6u;?-9YHKvkjEc)}wsb4Y-ArRK8`24uBT8N)8m%Ee zYJX21)|e{peL26}VUUKYQ3L@NSe8rEbN#AIo$tjJm-$B|IJU?mu(h$Sq`XNY0@NhY z0?WeMtPwP)sUdk}dWA4qBUV^x>P|is-kPgVe)*WV>dKDL>gOq1 zUYw(nU|N#dw>97A_(c3?VA_zDfF{^A1eE#8Bucd^ON(sv-{tc@&i)Y)3V~o7U~+AA zOwnXB5`WN^z$z<9^@(?LY%7?y5X_C(j1ip-Ug^f7Tt6suI3&a=&~#EJegG4r2^tKz zJoEXCVOc1QdOSNHp2d;t&smxL%CfK@mSl)Ky}`!6kCsi#7s5&G2Q!sM9S6o)&mdx% zz|2M~pav2;Th=DTN5yB@6HFAO!pl-y+tEJsh}(? z!tIyg01O*w@mWxsFhHMi7%Gqz!v(Osc5WxK+^1PGfsozw)FE}VIxk9GexmAohPNAF*SAjxG3Al#(xQoYXdI}TR zoCHAFS6+LDqsP8L1SZH{RxJjFK_=vy4nNH^?M!OsQWe^qC~$c1r&y`H9n5;D z2F$t-Htc%2@K(>opJHE{NytI2<_J<6Kz*p$wtKUTEH}zITx?H0L%!5%i@!rLphSBrkFs>jscP6?HVQovX8!~b~ZY|0h%&souT7e5nD@OxuSgC zVW*eo0B|1POwg7;6fJSUC`g+`1%XQvwpRc*&|AtV*h!#5nQM(@m!K)-Qop!Rt3F`a z9HUO zF3w{uI_==EpjFQWV4boF^A?wc@@@U+KrKPjn6sK{OLu-~1UloSqt-aHYo*^@kQy2+ zH(9*-mFz?YV4cL7EW)9hsdmG{5jaYXLvm*&3PZ4y?8z`$9z6`q9fgsJm@*W$-QSzu zut}57hroSbTd=&RJpuy#?K?A6!-;_MowpK8eb~5T-^eye%3O-T^ktSMbd%PT0j-B?#yAKr37u%gB z*2)WJMw6Y)6BvY$JjD`(06ci7u;u$hv}gN5oS&Q^*y$J6L)0#BD<>XL|;pZgtZaxp3~$0zxA(;6Qr_AP$?8l@S)C^Hoaz#rQFK^lA}3&)Gr}Fsca? zK>9BkVcl;c*E2P9UMppEIB&38dL9R?Xg9N{Nl~4*w!qsZJElz}Xc9gz#}cwnP4u{+ z6VNTEx*>u67?3bn{sWk*P`1_$YfsB+)Ax0+jt|)0p&VS?N0k8IAp2KH_#eY3I#{Hw zB$vObUDtXyZX)*wVh*@BefnUej#jv@%uiA=>ngX0kQXaz>8(WM)fX~v__@I}7|!Il z@J%r#I!JqqFwGd4JPhmDmL>1Bh}nn_BE;hgKUesNOf9zQhiuhn%4B}O8jnxEwJiQFDaiiuXw2sb?*8a}Lr;_#7+IPfIjhVDhazSpbQZECL+4)p8lO;)!y>Rt=0X*;O# zX{s(p-*d{#{Y3gVhL;A{4a(Z5sIfpk;WMCqdFA&Mb7mp;YMXhBF@p`}$ShAug+bo`;<9fm!~F z-;1yCj$GQ^mzucrfuatilXrYLr)`izjn_m(f~);txN?D7d?Kg4wDuPXilVyeVwjzf z=4Kewf=u}X_H*viVfPWZW?Sqa3G#h3|;b!Q7>BRc7-Wox0}&>}Lqo=0v;T_i~% zqB&h;14|~nK{W0N=$obGP@O%(c8SraYS^qiu%Q`B zBHdA!`Vk7#Bz*@_3eE#bizLzjBV;F0vfSA~+7@8+F{$7Y?fwI~Pp_X`2ORgqW6g@2 z{cQV!niSsMEVr1IaeRAj8~|*4yW~X5$6o`crw4uTHhgPs^qAk?9UPu;xy5wh2^jZ; z)@27Q=QKa?8w7_C0|u`@k=%b9Ce$D7x42CdLsckF2<$wLuV2kpik8PXex2^Co$n2o z)l#H*;#>?yrPw0x6LI@x(X$nezCBa0Obi%|I5ZV|4bJSPtNHjDkS|3S?fiv(i_(n* zFbve0g!B0!MMmakRsgg_if8nwImb=kk%|s+08xGQ)J?vpkdaya3UD|RJK+LQ72|g> zc4LnwInx!2pN-5Yvp7rvRF#B=(ZO8gyVB^0Dh#ZdHA2BjjppfV<=2Nm#w_t{%6O$W z`-?7N?LwL0DWgK0Y7L#ChSHfa{=DOpJpl8L@V70cd%ei)n%SQO;Z+Xw#li#%LUfbs z&hP%UzN(qM3cw#bWQS6_B@>1^ea-AqNA12xoiQeb_Zdtf>yHljqeIHqlyC^gzH)h1 zstXTFEb0r=l9;><<$a}YWlscH7VW_xeKVZ#*#v#HiuUOs7PPj8ml4#!BiGEK)kDpO zX=2mU0ZuIDDnhfV7v_Rs)0R#ff6I6_|MrzV(R$3Nt#S7D?GQy6?a^WRvA@r2~?7f~s99*9;fuqJ(843U`hRl2O|sk>J@WMsR2O zwyZt$@J)DnSUNkF@B3MPNz|<@`72{M*S5d<1Vkg+G=q~u{8OP84Yh6VCE5pNC*#m> z*jzHy5Tc82sBVw+6W7DoR5@LXZ|+>;)Q%czg%8pyMyeE2-)R^oHg~SrO~#I8MxNc> z6pWT&F&H1mX7#2@mBY>#rRoFKszT z(gvV#j3x|7sF|Dt0*CgsJTdH1R!>inYZWp*2RDbjjQCP98L_ds!$x&{t85NRYk4ii ztJ3HyC8h2A2&`kq^Cfci>N*r&btHg_|v6=s|v=(-MQ zK4kjqoI^~y`j9poC2r{Izdlehm8!AcMP^+SwDUce1Zon(%YvxK)x|rXsJRlO?-K91 zMsmHgI&PmqT_W}C0mdA_6L!EEjgJzidRvTN;vQRJ-uBl#{dEeN?24PRwx)7c5kF^ut=M0)e@zr?z_vpYf=%;;@UYF9>9-->Qf2FW*# z5*#VFB$$-k(zphh4sAElMiLbp`$+SKm*{l6qX;Q8GZ7b|J>OhC!yg$}8dt$dx3E8b z$FlaM*K@6mSsYCoe#*QjLEB3|_Vs4GbZI#!>Ya}dzh%uMn}sw0gFQQ{+V+e|_`q)M3nK27)nAqQ-viJoPHUKdr9HN`v0 z+tZo0ORLuv_d)x}gO|~s(H!12RM(aMfqLG>KSH#kGxC{sUUj>FUC(6;ds1cOjeDYu zOrd>q@bNFq5?0s&@5nbF3-rw{{V&YYf3o_9|K-X4k861UwZ&C2bH+A7^%7nizU>b? zC2@*VlrqprJiv$rx{+^+Op9i3RM;IHq@a;34=Gn%B+rXMZi=UsHC@TEFk4{*fs96p z)wNUY?AhVkdLGQmPESuh@-!iqSZrnxIT~Mon)J+i+B~9VdL8QE`^4=2@lNaKluUVx z_^i7~5E4dN4&gVMi%;7ast@WIY21Q`+^iTC*Gx@IMVYB`BLFHzPh{Fpc6LKZTk@>P zquo2E*Pgq(0MX>h>4)YaJYbIK&V?-W}JfL@&R0I2)TOA!Teg zNa4DBO&)`Nn0$Inb|d8ea|)qqOLYVbQIBRC4T4E<5#Nzc2 z57|Bq7mYsW8y?uLA$XMj%OeK+1|DAKcLYB98-vDP<3*+SKYcPcOkm&}H|!{9l*9%L zbiYJYJ^)Cql-&wPwABGD>Ai7SUXe15m zIr^wNEU$9)D6@atm z(w(1~GuLpHi?JGgIBj`Ovy;j4M`XjrCNs?JsGh1zKsZ{8 z@%G?i>LaU7#uSQLpypocm*onI)$8zFgVWc7_8PVuuw>u`j-<@R$Of}T`glJ!@v*N^ zc(T~+N+M!ZczPSXN&?Ww(<@B=+*jZ+KmcpB8* zDY_1bZ3fwTw|urH{LLWB;DCGzz$jD|VX#Af@HC%BktA8F7VJSy&!5iTt};#U^e0_q zh6j7KCTInKqriZ1`BiF3iq2LWk;gyt0ORIFc4Mi3Bx`7WEuFq{u^C49-SYVjnv!_40m1>7x*+<8~Xkq?056 z!RBfE@osP%SxzOw>cLAQ$bioAOC0V!OzIXIc};)8HjfPtc~8tnah$PtoAz`4k)7$FDUc2O@D)g_uAo&nXMymK$##V?gYUPt^l zj{6NFDL(l-Rh(xkAHP%bBa=($r%3Y~jB!eQ1Smuq2iuQ|>n%Y=p(26SE5gFu11*Q< zaPN5G^d;Iovf`VY&Gh58z~%JpGzaeUz6QoBL^J%+U4|30w7Q&g9i}}@l61eKEfCgo zST6qMxF_Eaj7;0OC)TSU{4_m}%FOa6B{AxS$QIcmmG~IVjjf;7Uk!HBtHfm{%LsLb zu8~5VQFyOZk&!VY(wxL__haJ;>Bj?g&n`+i&=X{unJmv&0whCitWfGlOr6+Tc-lMZ z(ZRXqC-=O+GAvTXKViA9vdwu{aifhk$tYh~-9BScg!Yr*M2zw&9`pHMxHGh`dUH-1;~^6lF@ep;X9PjQ!rqmXNWJ?#P-qb%*TB%xe&3 zX*5V>xuW7)$3!Yc$y>cwBqd8+p+u>WS7p7~O80ipG{(a*#=NJ`^Ld6k-`|;Y&htFy zIi2(Sm)4eD=o+CGo~M3%qF|O9P0+ahmc%EklI?NgX05W3+OdS`_Rd#wg-}hd1&txU5wXy zy`x)05?WVZvELw`XWetIAg6$|(^4ntaE;=f$Wcpwbxm7?bLDnPs-1!bRoMcy!EeOh zpIv8ewDzcIU}mv1NxV!&(Wf7~_kqGAk=2=j&O5FA)z2!APCcDQPnIaiqMkVT4fUyX z))R|WvOJyzcU6d=z0q8JDt42*`js4g+_t{YP7lVguX+vhEejJ3TAIo*Z6jizHm#S- zZT_}-STQAa-0Gn8+RmR7V}{Ns1@jJ{^Sb!9&RSXXP;^ep)r6;&PW++~XYXC9a=zSF z?sp(JQo&MROb~b1Y*Xw4!P)>PHT>Z<)*U=Ax_75^OUw97pNudbxS1XPtNrIg zQ5YB77E@i7$2Ia}(^JcCi@OX`9a|m}PY%-th2m~y+)eCl>fTVjCP^lDOBLyhg1DZ+ z)~G{&OkDc$!;t~`gq(wz@qW3lh9B^ic$>-h#nV!H8d#l+>C(M%g}u2g=I#&W|L!VD zqHYoQkBW;`r|fW02u{7X!X;}T7X4iAaWzkeOh}7&o!F1qt4#$1|BDF;(2VlgEqJ$F zy8Ba-y(%fs`MzpvyXlQLEhS^ed$7Va2hO%?$-D>^*f$b)2Hx;}Ao$UqFt7l26<7eP z!{!C7PVrq>=794Zqmc z%LKkzIBZq@%Ja8EkH}?>c5ILG(EAMS*JHu?#9_7TsELw)8LZzN>f2Y6YN{AJC?34> zh42sPa1%2JpCeS9&E1URm+Pb}B>A1M`R{+O+2~}c(@^1Rf&J9p(4QqHl;E^4w5;I5 zM{?(A^eg*6DY_kI*-9!?If^HaNBfuh*u==X1_a?8$EQ3z!&;v2iJ``O7mZh%G)(O8 ze<4wX?N94(Ozf9`j+=TZpCbH>KVjWyLUe*SCiYO=rFZ4}S~Tq|ln75Jz7$AcKl$=hub=-0RM1s(0WMmE`(OPtAj>7_2I5&76hu2KPIA0y;9{+8yKa;9-m??hIE5t`5DrZ8DzRsQ+{p1jk-VFL9U z2NK_oIeqvyze>1K%b|V?-t;Wv`nY~?-t;tMC4ozyk8CR(hoZTno3!*8ZTc15`?MFf zDI892&g&3lshOEv4E@w-*_%)8C_<&HhV`0D5lN$WT4Q^UWHNSAE+RZe(o z%bqR^hp1IsDr47e^AajFtlppT)2F6yPcrWO9{Kw{o=P6y^HOW$Wqd_)_fwzn`ikZl zOGVc0+S(*=xZ_KbL0Nr`Sx$$CWEbw$52udl1f=X6CZEcFMA*nl>`0gn4&tc5^`!!)tGw<}^Q>P7E}$ zialDUofH*XcB3r9@tA@lnS}dA(@nK_xuw0b;FPUnNGD0;MIySCw=cSzB#=3>F37V-nni3UNB)-;;Gkk;3l9fh6FIjSZU zk=Eo2a`6i7@i*4>ym5`R?i-uZFv6+iX*Gi^I}ZU1OrLAX8aGiT@`*YnjeF>}$U}ORP`+EY5`eqVC_&4yG z;Tp>+2QbZ?lt1GB+D}q14W3dWP8lWnN zf(nlT6+XW&(zme{FbyDpP^NakA<~TK=Y}H^eS%2rt0v8Lr)B}@B!cTvC=9FM;7q4@ zf*;vb4HG>RFpY5?vFCp27VEnVIGx~-na6biU4{+UoYe=}^R#_My6wT$5d&r*=kpAA zu;=-c0|~yqi(N8&*H;aNfhyey+HHQ7J_qae*_CgG2V8j=Tq936S0DC8r3BXBql3Gz z0pLo_`|4Q+oY3rPBNaLmL{QM};9dke>ujP^j@z-N;fNlKb|edn>)YaafDaJ>GWKP$ z5}l&#$QFhN!CMT;WH&z-5E)kvM|36lV!^#3z{@2FF>HsgUO4PMqO#U$X%+U>K!xJ@ zBFs|+woG_9HZQs_Tw*vnCPGhlXG@>y|6pJT$I67!aP&b0o$AF2JwFy9OoapQAk>k7 z**+$_5L;5fKof<;NBX%_;vP@eyD=Z0(QW)5AF7 zp|=tk3p?5)*e~Inuydz-U?%Kuj4%zToS5I|lolPT!B)ZuRVkVa>f*-2aPeV3R79xh zB)3A$>X~szg#}>uNkpLPG#3IKyeMHM*pUuV5=-Jji7S6PSQ9oCLo{oXxzOZfF$PP) zrYwlmSQ-~n94uO3CD{K0QTmj@g%Yzn7_xQ4fTduU0Yqvln`e_`CdXH5iQ5qRr1 zBC;}%YZ2!4I>*=sR)O~jBPx6sxmIEBnq)s-fHz_y0z8-gPl2Us4BiBXNR5CIF!YR@ zb9B305SilU*@4|+ x6JBtc8JSt5M0pkooaq!^FqtuD_KdXXTo>Mw54>`rP&>h&58!3a6l6r9{sG7g--!SK diff --git a/commonmark-android-test/gradle/wrapper/gradle-wrapper.properties b/commonmark-android-test/gradle/wrapper/gradle-wrapper.properties index 1b16c34a7..a80b22ce5 100644 --- a/commonmark-android-test/gradle/wrapper/gradle-wrapper.properties +++ b/commonmark-android-test/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/commonmark-android-test/gradlew b/commonmark-android-test/gradlew index 2fe81a7d9..1aa94a426 100755 --- a/commonmark-android-test/gradlew +++ b/commonmark-android-test/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,78 +17,111 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -97,87 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/commonmark-android-test/gradlew.bat b/commonmark-android-test/gradlew.bat index 24467a141..7101f8e46 100644 --- a/commonmark-android-test/gradlew.bat +++ b/commonmark-android-test/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,10 +25,14 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -51,48 +55,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 2d8d81762a97315afa2ff5d3614d0e2856b88c74 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 12:27:07 +1100 Subject: [PATCH 059/345] Downgrade Android tools to version that should work with Java 11 --- commonmark-android-test/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-android-test/build.gradle b/commonmark-android-test/build.gradle index 3ac119f44..f359e8154 100644 --- a/commonmark-android-test/build.gradle +++ b/commonmark-android-test/build.gradle @@ -4,7 +4,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:8.2.2' + classpath 'com.android.tools.build:gradle:7.4.2' } } From 9f2a3eda1b96d98cf6b71de85fe8adeb4fc8890b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 16:15:25 +1100 Subject: [PATCH 060/345] Try to exclude module-info --- commonmark-android-test/app/build.gradle | 25 ++++++++++++------- .../app/src/main/AndroidManifest.xml | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/commonmark-android-test/app/build.gradle b/commonmark-android-test/app/build.gradle index 5b7cc4cd6..2ced89830 100644 --- a/commonmark-android-test/app/build.gradle +++ b/commonmark-android-test/app/build.gradle @@ -28,15 +28,22 @@ android { // we add other modules sources in order for lint to process them (lint operates on sources) sourceSets { main { - java.srcDirs += [ - '../../commonmark', - '../../commonmark-ext-autolink', - '../../commonmark-ext-gfm-strikethrough', - '../../commonmark-ext-gfm-tables', - '../../commonmark-ext-heading-anchor', - '../../commonmark-ext-ins', - '../../commonmark-ext-yaml-front-matter' - ].collect { "$it/src/main/java" } + java { + [ + '../../commonmark', + '../../commonmark-ext-autolink', + '../../commonmark-ext-gfm-strikethrough', + '../../commonmark-ext-gfm-tables', + '../../commonmark-ext-heading-anchor', + '../../commonmark-ext-ins', + '../../commonmark-ext-yaml-front-matter' + ].forEach { d -> + // don't include module-info files, otherwise we get + // "too many module declarations found" + PatternSet patternSet = new PatternSet().exclude('**/module-info.java') + srcDirs += fileTree("$d/src/main/java").matching(patternSet) + } + } } } } diff --git a/commonmark-android-test/app/src/main/AndroidManifest.xml b/commonmark-android-test/app/src/main/AndroidManifest.xml index 0343d4fdc..486520569 100644 --- a/commonmark-android-test/app/src/main/AndroidManifest.xml +++ b/commonmark-android-test/app/src/main/AndroidManifest.xml @@ -1,3 +1,3 @@ - + From 33a589951141e6514f9ebc012f9600fc67def1cc Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 18:56:26 +1100 Subject: [PATCH 061/345] Use non-deprecated gradle properties --- commonmark-android-test/app/build.gradle | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/commonmark-android-test/app/build.gradle b/commonmark-android-test/app/build.gradle index 2ced89830..fd8ae34cb 100644 --- a/commonmark-android-test/app/build.gradle +++ b/commonmark-android-test/app/build.gradle @@ -2,13 +2,12 @@ apply plugin: 'com.android.application' android { namespace "org.commonmark.android.test" - compileSdkVersion 30 - buildToolsVersion "34.0.0" + compileSdk 30 defaultConfig { applicationId "org.commonmark.android.test" - minSdkVersion 19 - targetSdkVersion 30 + minSdk 19 + targetSdk 30 versionCode 1 versionName "1.0" } From ea1650335c85a84be338dbef5770c6b9e01ddee7 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 19:00:28 +1100 Subject: [PATCH 062/345] Don't use String.repeat for now --- .../markdown/CoreMarkdownNodeRenderer.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 31a7ceb50..e0cc4eb25 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -149,12 +149,11 @@ public void visit(IndentedCodeBlock indentedCodeBlock) { @Override public void visit(FencedCodeBlock fencedCodeBlock) { String literal = fencedCodeBlock.getLiteral(); - int count = fencedCodeBlock.getFenceLength(); - String fence = String.valueOf(fencedCodeBlock.getFenceChar()).repeat(count); + String fence = repeat(String.valueOf(fencedCodeBlock.getFenceChar()), fencedCodeBlock.getFenceLength()); int indent = fencedCodeBlock.getFenceIndent(); if (indent > 0) { - String indentPrefix = " ".repeat(indent); + String indentPrefix = repeat(" ", indent); writer.writePrefix(indentPrefix); writer.pushPrefix(indentPrefix); } @@ -232,20 +231,18 @@ public void visit(ListItem listItem) { boolean pushedPrefix = false; if (listHolder instanceof BulletListHolder) { BulletListHolder bulletListHolder = (BulletListHolder) listHolder; - int count = listItem.getMarkerIndent(); - String marker = " ".repeat(count) + bulletListHolder.bulletMarker; + String marker = repeat(" ", listItem.getMarkerIndent()) + bulletListHolder.bulletMarker; writer.writePrefix(marker); - writer.writePrefix(" ".repeat(contentIndent - marker.length())); - writer.pushPrefix(" ".repeat(contentIndent)); + writer.writePrefix(repeat(" ", contentIndent - marker.length())); + writer.pushPrefix(repeat(" ", contentIndent)); pushedPrefix = true; } else if (listHolder instanceof OrderedListHolder) { OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; - int count = listItem.getMarkerIndent(); - String marker = " ".repeat(count) + orderedListHolder.number + orderedListHolder.delimiter; + String marker = repeat(" ", listItem.getMarkerIndent()) + orderedListHolder.number + orderedListHolder.delimiter; orderedListHolder.number++; writer.writePrefix(marker); - writer.writePrefix(" ".repeat(contentIndent - marker.length())); - writer.pushPrefix(" ".repeat(contentIndent)); + writer.writePrefix(repeat(" ", contentIndent - marker.length())); + writer.pushPrefix(repeat(" ", contentIndent)); pushedPrefix = true; } if (listItem.getFirstChild() == null) { @@ -439,6 +436,15 @@ private static boolean contains(String s, CharMatcher charMatcher) { return false; } + // Keep for Android compat (String.repeat only available on Android 12 and later) + private static String repeat(String s, int count) { + StringBuilder sb = new StringBuilder(s.length() * count); + for (int i = 0; i < count; i++) { + sb.append(s); + } + return sb.toString(); + } + private static List getLines(String literal) { // Without -1, split would discard all trailing empty strings, which is not what we want, e.g. it would // return the same result for "abc", "abc\n" and "abc\n\n". From af61f1468bbd600085181d9f4d8b7b717156e28e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 19:17:15 +1100 Subject: [PATCH 063/345] Update README --- CHANGELOG.md | 4 ++-- README.md | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 157c166b1..52502922b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. -## Unreleased +## [Unreleased] ### Changed - Modular JAR: Require at least Java 11 and add a module descriptor (module-info), remove no longer necessary `Automatic-Module-Name` header @@ -387,7 +387,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. -[0.22.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.21.0...commonmark-parent-0.22.0 +[Unreleased]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.21.0...HEAD [0.21.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.20.0...commonmark-parent-0.21.0 [0.20.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.19.0...commonmark-parent-0.20.0 [0.19.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.2...commonmark-parent-0.19.0 diff --git a/README.md b/README.md index 5cce587d9..e4b07bfdc 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,11 @@ extensible library with the following features: * Flexible (manipulate the AST after parsing, customize HTML rendering) * Extensible (tables, strikethrough, autolinking and more, see below) -The library is supported on Java 11 and later. It works on Android too, but -that is currently not part of the builds and so only supported on a best-effort -basis, please report any problems. For Android the minimum API level is 19, see -the [commonmark-android-test](commonmark-android-test) +The library is supported on Java 11 and later. It works on Android too, +but that is on a best-effort basis, please report problems. For Android the +minimum API level is 19, see the +[commonmark-android-test](commonmark-android-test) directory. -[commonmark-android-test](commonmark-android-test) directory. Coordinates for core library (see all on [Maven Central]): From 5993630910be672354dee4ba206aa9889dbea911 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 19:22:49 +1100 Subject: [PATCH 064/345] Update spec to 0.31.2 --- .../src/main/resources/gfm-spec.txt | 66 +++---- .../src/main/resources/spec.txt | 170 +++++++++--------- 2 files changed, 111 insertions(+), 125 deletions(-) diff --git a/commonmark-test-util/src/main/resources/gfm-spec.txt b/commonmark-test-util/src/main/resources/gfm-spec.txt index 170276156..d42f3369e 100644 --- a/commonmark-test-util/src/main/resources/gfm-spec.txt +++ b/commonmark-test-util/src/main/resources/gfm-spec.txt @@ -130,7 +130,7 @@ questions it does not answer: not require that. This is hardly a "corner case," and divergences between implementations on this issue often lead to surprises for users in real documents. (See [this comment by John - Gruber](http://article.gmane.org/gmane.text.markdown.general/1997).) + Gruber](https://web.archive.org/web/20170611172104/http://article.gmane.org/gmane.text.markdown.general/1997).) 2. Is a blank line needed before a block quote or heading? Most implementations do not require the blank line. However, @@ -138,7 +138,7 @@ questions it does not answer: also to ambiguities in parsing (note that some implementations put the heading inside the blockquote, while others do not). (John Gruber has also spoken [in favor of requiring the blank - lines](http://article.gmane.org/gmane.text.markdown.general/2146).) + lines](https://web.archive.org/web/20170611172104/http://article.gmane.org/gmane.text.markdown.general/2146).) 3. Is a blank line needed before an indented code block? (`Markdown.pl` requires it, but this is not mentioned in the @@ -171,7 +171,7 @@ questions it does not answer: ``` (There are some relevant comments by John Gruber - [here](http://article.gmane.org/gmane.text.markdown.general/2554).) + [here](https://web.archive.org/web/20170611172104/http://article.gmane.org/gmane.text.markdown.general/2554).) 5. Can list markers be indented? Can ordered list markers be right-aligned? @@ -1001,10 +1001,7 @@ interpretable as a [code fence], [ATX heading][ATX headings], A [setext heading underline](@) is a sequence of `=` characters or a sequence of `-` characters, with no more than 3 -spaces indentation and any number of trailing spaces. If a line -containing a single `-` can be interpreted as an -empty [list items], it should be interpreted this way -and not as a [setext heading underline]. +spaces of indentation and any number of trailing spaces or tabs. The heading is a level 1 heading if `=` characters are used in the [setext heading underline], and a level 2 heading if `-` @@ -1638,7 +1635,7 @@ has been found, the code block contains all of the lines after the opening code fence until the end of the containing block (or document). (An alternative spec would require backtracking in the event that a closing code fence is not found. But this makes parsing -much less efficient, and there seems to be no real down side to the +much less efficient, and there seems to be no real downside to the behavior described here.) A fenced code block may interrupt a paragraph, and does not require @@ -2068,7 +2065,7 @@ followed by an uppercase ASCII letter.\ ``. -6. **Start condition:** line begins the string `<` or `foo, bar, baz

    +

    foo, bar, baz

    ```````````````````````````````` @@ -7200,7 +7197,7 @@ foo***bar***baz ```````````````````````````````` example foo******bar*********baz . -

    foobar***baz

    +

    foobar***baz

    ```````````````````````````````` @@ -7271,21 +7268,21 @@ __foo _bar_ baz__ ```````````````````````````````` example __foo __bar__ baz__ . -

    foo bar baz

    +

    foo bar baz

    ```````````````````````````````` ```````````````````````````````` example ____foo__ bar__ . -

    foo bar

    +

    foo bar

    ```````````````````````````````` ```````````````````````````````` example **foo **bar**** . -

    foo bar

    +

    foo bar

    ```````````````````````````````` @@ -7570,14 +7567,14 @@ switching delimiters: ```````````````````````````````` example ****foo**** . -

    foo

    +

    foo

    ```````````````````````````````` ```````````````````````````````` example ____foo____ . -

    foo

    +

    foo

    ```````````````````````````````` @@ -7588,7 +7585,7 @@ delimiters: ```````````````````````````````` example ******foo****** . -

    foo

    +

    foo

    ```````````````````````````````` @@ -7604,7 +7601,7 @@ Rule 14: ```````````````````````````````` example _____foo_____ . -

    foo

    +

    foo

    ```````````````````````````````` @@ -9410,10 +9407,9 @@ character, and a `>` character. A [closing tag](@) consists of the string ``. -An [HTML comment](@) consists of ``, -where *text* does not start with `>` or `->`, does not end with `-`, -and does not contain `--`. (See the -[HTML5 spec](http://www.w3.org/TR/html5/syntax.html#comments).) +An [HTML comment](@) consists of ``, ``, or ``, and `-->` (see the +[HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)). A [processing instruction](@) consists of the string ` +foo . -

    foo

    +

    foo

    ```````````````````````````````` - -```````````````````````````````` example -foo -. -

    foo <!-- not a comment -- two hyphens -->

    -```````````````````````````````` - - -Not comments: - ```````````````````````````````` example foo foo --> -foo +foo foo --> . -

    foo <!--> foo -->

    -

    foo <!-- foo--->

    +

    foo foo -->

    +

    foo foo -->

    ```````````````````````````````` diff --git a/commonmark-test-util/src/main/resources/spec.txt b/commonmark-test-util/src/main/resources/spec.txt index e6f313757..f1fab281e 100644 --- a/commonmark-test-util/src/main/resources/spec.txt +++ b/commonmark-test-util/src/main/resources/spec.txt @@ -1,9 +1,9 @@ --- title: CommonMark Spec author: John MacFarlane -version: 0.30 -date: '2021-06-19' -license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' +version: '0.31.2' +date: '2024-01-28' +license: '[CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)' ... # Introduction @@ -14,7 +14,7 @@ Markdown is a plain text format for writing structured documents, based on conventions for indicating formatting in email and usenet posts. It was developed by John Gruber (with help from Aaron Swartz) and released in 2004 in the form of a -[syntax description](http://daringfireball.net/projects/markdown/syntax) +[syntax description](https://daringfireball.net/projects/markdown/syntax) and a Perl script (`Markdown.pl`) for converting Markdown to HTML. In the next decade, dozens of implementations were developed in many languages. Some extended the original @@ -34,10 +34,10 @@ As Gruber writes: > Markdown-formatted document should be publishable as-is, as > plain text, without looking like it's been marked up with tags > or formatting instructions. -> () +> () The point can be illustrated by comparing a sample of -[AsciiDoc](http://www.methods.co.nz/asciidoc/) with +[AsciiDoc](https://asciidoc.org/) with an equivalent sample of Markdown. Here is a sample of AsciiDoc from the AsciiDoc manual: @@ -103,7 +103,7 @@ source, not just in the processed document. ## Why is a spec needed? John Gruber's [canonical description of Markdown's -syntax](http://daringfireball.net/projects/markdown/syntax) +syntax](https://daringfireball.net/projects/markdown/syntax) does not specify the syntax unambiguously. Here are some examples of questions it does not answer: @@ -114,7 +114,7 @@ questions it does not answer: not require that. This is hardly a "corner case," and divergences between implementations on this issue often lead to surprises for users in real documents. (See [this comment by John - Gruber](http://article.gmane.org/gmane.text.markdown.general/1997).) + Gruber](https://web.archive.org/web/20170611172104/http://article.gmane.org/gmane.text.markdown.general/1997).) 2. Is a blank line needed before a block quote or heading? Most implementations do not require the blank line. However, @@ -122,7 +122,7 @@ questions it does not answer: also to ambiguities in parsing (note that some implementations put the heading inside the blockquote, while others do not). (John Gruber has also spoken [in favor of requiring the blank - lines](http://article.gmane.org/gmane.text.markdown.general/2146).) + lines](https://web.archive.org/web/20170611172104/http://article.gmane.org/gmane.text.markdown.general/2146).) 3. Is a blank line needed before an indented code block? (`Markdown.pl` requires it, but this is not mentioned in the @@ -155,7 +155,7 @@ questions it does not answer: ``` (There are some relevant comments by John Gruber - [here](http://article.gmane.org/gmane.text.markdown.general/2554).) + [here](https://web.archive.org/web/20170611172104/http://article.gmane.org/gmane.text.markdown.general/2554).) 5. Can list markers be indented? Can ordered list markers be right-aligned? @@ -316,9 +316,9 @@ A line containing no characters, or a line containing only spaces The following definitions of character classes will be used in this spec: -A [Unicode whitespace character](@) is -any code point in the Unicode `Zs` general category, or a tab (`U+0009`), -line feed (`U+000A`), form feed (`U+000C`), or carriage return (`U+000D`). +A [Unicode whitespace character](@) is a character in the Unicode `Zs` general +category, or a tab (`U+0009`), line feed (`U+000A`), form feed (`U+000C`), or +carriage return (`U+000D`). [Unicode whitespace](@) is a sequence of one or more [Unicode whitespace characters]. @@ -337,9 +337,8 @@ is `!`, `"`, `#`, `$`, `%`, `&`, `'`, `(`, `)`, `[`, `\`, `]`, `^`, `_`, `` ` `` (U+005B–0060), `{`, `|`, `}`, or `~` (U+007B–007E). -A [Unicode punctuation character](@) is an [ASCII -punctuation character] or anything in -the general Unicode categories `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`. +A [Unicode punctuation character](@) is a character in the Unicode `P` +(puncuation) or `S` (symbol) general categories. ## Tabs @@ -579,9 +578,9 @@ raw HTML: ```````````````````````````````` example - + . - +

    https://example.com?find=\*

    ```````````````````````````````` @@ -1330,10 +1329,7 @@ interpretable as a [code fence], [ATX heading][ATX headings], A [setext heading underline](@) is a sequence of `=` characters or a sequence of `-` characters, with no more than 3 -spaces of indentation and any number of trailing spaces or tabs. If a line -containing a single `-` can be interpreted as an -empty [list items], it should be interpreted this way -and not as a [setext heading underline]. +spaces of indentation and any number of trailing spaces or tabs. The heading is a level 1 heading if `=` characters are used in the [setext heading underline], and a level 2 heading if `-` @@ -1967,7 +1963,7 @@ has been found, the code block contains all of the lines after the opening code fence until the end of the containing block (or document). (An alternative spec would require backtracking in the event that a closing code fence is not found. But this makes parsing -much less efficient, and there seems to be no real down side to the +much less efficient, and there seems to be no real downside to the behavior described here.) A fenced code block may interrupt a paragraph, and does not require @@ -2397,7 +2393,7 @@ followed by an ASCII letter.\ ``. -6. **Start condition:** line begins the string `<` or ``, or the string `/>`.\ @@ -4118,7 +4114,7 @@ The following rules define [list items]: blocks *Bs* starting with a character other than a space or tab, and *M* is a list marker of width *W* followed by 1 ≤ *N* ≤ 4 spaces of indentation, then the result of prepending *M* and the following spaces to the first line - of Ls*, and indenting subsequent lines of *Ls* by *W + N* spaces, is a + of *Ls*, and indenting subsequent lines of *Ls* by *W + N* spaces, is a list item with *Bs* as its contents. The type of the list item (bullet or ordered) is determined by the type of its list marker. If the list item is ordered, then it is also assigned a start @@ -4533,7 +4529,7 @@ inside the code block: Note that rules #1 and #2 only apply to two cases: (a) cases in which the lines to be included in a list item begin with a -characer other than a space or tab, and (b) cases in which +character other than a space or tab, and (b) cases in which they begin with an indented code block. In a case like the following, where the first block begins with three spaces of indentation, the rules do not allow us to form a list item by @@ -5353,11 +5349,11 @@ by itself should be a paragraph followed by a nested sublist. Since it is well established Markdown practice to allow lists to interrupt paragraphs inside list items, the [principle of uniformity] requires us to allow this outside list items as -well. ([reStructuredText](http://docutils.sourceforge.net/rst.html) +well. ([reStructuredText](https://docutils.sourceforge.net/rst.html) takes a different approach, requiring blank lines before lists even inside other list items.) -In order to solve of unwanted lists in paragraphs with +In order to solve the problem of unwanted lists in paragraphs with hard-wrapped numerals, we allow only lists starting with `1` to interrupt paragraphs. Thus, @@ -6058,18 +6054,18 @@ But this is an HTML tag: And this is code: ```````````````````````````````` example -`` +`` . -

    <http://foo.bar.baz>`

    +

    <https://foo.bar.baz>`

    ```````````````````````````````` But this is an autolink: ```````````````````````````````` example -` +` . -

    http://foo.bar.`baz`

    +

    https://foo.bar.`baz`

    ```````````````````````````````` @@ -6102,7 +6098,7 @@ closing backtick strings to be equal in length: ## Emphasis and strong emphasis John Gruber's original [Markdown syntax -description](http://daringfireball.net/projects/markdown/syntax#em) says: +description](https://daringfireball.net/projects/markdown/syntax#em) says: > Markdown treats asterisks (`*`) and underscores (`_`) as indicators of > emphasis. Text wrapped with one `*` or `_` will be wrapped with an HTML @@ -6204,7 +6200,7 @@ Here are some examples of delimiter runs. (The idea of distinguishing left-flanking and right-flanking delimiter runs based on the character before and the character after comes from Roopesh Chander's -[vfmd](http://www.vfmd.org/vfmd-spec/specification/#procedure-for-identifying-emphasis-tags). +[vfmd](https://web.archive.org/web/20220608143320/http://www.vfmd.org/vfmd-spec/specification/#procedure-for-identifying-emphasis-tags). vfmd uses the terminology "emphasis indicator string" instead of "delimiter run," and its rules for distinguishing left- and right-flanking runs are a bit more complex than the ones given here.) @@ -6346,6 +6342,21 @@ Unicode nonbreaking spaces count as whitespace, too: ```````````````````````````````` +Unicode symbols count as punctuation, too: + +```````````````````````````````` example +*$*alpha. + +*£*bravo. + +*€*charlie. +. +

    *$*alpha.

    +

    *£*bravo.

    +

    *€*charlie.

    +```````````````````````````````` + + Intraword emphasis with `*` is permitted: ```````````````````````````````` example @@ -7431,16 +7442,16 @@ _a `_`_ ```````````````````````````````` example -**a +**a . -

    **ahttp://foo.bar/?q=**

    +

    **ahttps://foo.bar/?q=**

    ```````````````````````````````` ```````````````````````````````` example -__a +__a . -

    __ahttp://foo.bar/?q=__

    +

    __ahttps://foo.bar/?q=__

    ```````````````````````````````` @@ -7688,13 +7699,13 @@ A link can contain fragment identifiers and queries: ```````````````````````````````` example [link](#fragment) -[link](http://example.com#fragment) +[link](https://example.com#fragment) -[link](http://example.com?foo=3#frag) +[link](https://example.com?foo=3#frag) .

    link

    -

    link

    -

    link

    +

    link

    +

    link

    ```````````````````````````````` @@ -7938,9 +7949,9 @@ and autolinks over link grouping: ```````````````````````````````` example -[foo +[foo . -

    [foohttp://example.com/?search=](uri)

    +

    [foohttps://example.com/?search=](uri)

    ```````````````````````````````` @@ -8094,11 +8105,11 @@ and autolinks over link grouping: ```````````````````````````````` example -[foo +[foo [ref]: /uri . -

    [foohttp://example.com/?search=][ref]

    +

    [foohttps://example.com/?search=][ref]

    ```````````````````````````````` @@ -8298,7 +8309,7 @@ A [collapsed reference link](@) consists of a [link label] that [matches] a [link reference definition] elsewhere in the document, followed by the string `[]`. -The contents of the first link label are parsed as inlines, +The contents of the link label are parsed as inlines, which are used as the link's text. The link's URI and title are provided by the matching reference link definition. Thus, `[foo][]` is equivalent to `[foo][foo]`. @@ -8351,7 +8362,7 @@ A [shortcut reference link](@) consists of a [link label] that [matches] a [link reference definition] elsewhere in the document and is not followed by `[]` or a link label. -The contents of the first link label are parsed as inlines, +The contents of the link label are parsed as inlines, which are used as the link's text. The link's URI and title are provided by the matching link reference definition. Thus, `[foo]` is equivalent to `[foo][]`. @@ -8438,7 +8449,7 @@ following closing bracket: ```````````````````````````````` -Full and compact references take precedence over shortcut +Full and collapsed references take precedence over shortcut references: ```````````````````````````````` example @@ -8754,7 +8765,7 @@ a link to the URI, with the URI as the link's label. An [absolute URI](@), for these purposes, consists of a [scheme] followed by a colon (`:`) -followed by zero or more characters other [ASCII control +followed by zero or more characters other than [ASCII control characters][ASCII control character], [space], `<`, and `>`. If the URI includes these characters, they must be percent-encoded (e.g. `%20` for a space). @@ -8774,9 +8785,9 @@ Here are some valid autolinks: ```````````````````````````````` example - + . -

    http://foo.bar.baz/test?q=hello&id=22&boolean

    +

    https://foo.bar.baz/test?q=hello&id=22&boolean

    ```````````````````````````````` @@ -8816,9 +8827,9 @@ with their syntax: ```````````````````````````````` example - + . -

    http://../

    +

    https://../

    ```````````````````````````````` @@ -8832,18 +8843,18 @@ with their syntax: Spaces are not allowed in autolinks: ```````````````````````````````` example - + . -

    <http://foo.bar/baz bim>

    +

    <https://foo.bar/baz bim>

    ```````````````````````````````` Backslash-escapes do not work inside autolinks: ```````````````````````````````` example - + . -

    http://example.com/\[\

    +

    https://example.com/\[\

    ```````````````````````````````` @@ -8895,9 +8906,9 @@ These are not autolinks: ```````````````````````````````` example -< http://foo.bar > +< https://foo.bar > . -

    < http://foo.bar >

    +

    < https://foo.bar >

    ```````````````````````````````` @@ -8916,9 +8927,9 @@ These are not autolinks: ```````````````````````````````` example -http://example.com +https://example.com . -

    http://example.com

    +

    https://example.com

    ```````````````````````````````` @@ -8980,10 +8991,9 @@ A [closing tag](@) consists of the string ``. -An [HTML comment](@) consists of ``, -where *text* does not start with `>` or `->`, does not end with `-`, -and does not contain `--`. (See the -[HTML5 spec](http://www.w3.org/TR/html5/syntax.html#comments).) +An [HTML comment](@) consists of ``, ``, or ``, and `-->` (see the +[HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)). A [processing instruction](@) consists of the string ` +foo . -

    foo

    +

    foo

    ```````````````````````````````` - -```````````````````````````````` example -foo -. -

    foo <!-- not a comment -- two hyphens -->

    -```````````````````````````````` - - -Not comments: - ```````````````````````````````` example foo foo --> -foo +foo foo --> . -

    foo <!--> foo -->

    -

    foo <!-- foo--->

    +

    foo foo -->

    +

    foo foo -->

    ```````````````````````````````` @@ -9674,7 +9674,7 @@ through the stack for an opening `[` or `![` delimiter. delimiter from the stack, and return a literal text node `]`. - If we find one and it's active, then we parse ahead to see if - we have an inline link/image, reference link/image, compact reference + we have an inline link/image, reference link/image, collapsed reference link/image, or shortcut reference link/image. + If we don't, then we remove the opening delimiter from the From 414ba56a17b545601b5360d2f7ddeb7fca30b20b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 21:03:01 +1100 Subject: [PATCH 065/345] Update Unicode punctuation to include symbols --- .../java/org/commonmark/text/Characters.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/text/Characters.java b/commonmark/src/main/java/org/commonmark/text/Characters.java index 4d9532329..ee56ca67e 100644 --- a/commonmark/src/main/java/org/commonmark/text/Characters.java +++ b/commonmark/src/main/java/org/commonmark/text/Characters.java @@ -57,17 +57,23 @@ public static boolean isSpaceOrTab(CharSequence s, int index) { } /** - * @see punctuation character + * @see Unicode punctuation character */ public static boolean isPunctuationCodePoint(int codePoint) { switch (Character.getType(codePoint)) { - case Character.CONNECTOR_PUNCTUATION: + // General category "P" (punctuation) case Character.DASH_PUNCTUATION: + case Character.START_PUNCTUATION: case Character.END_PUNCTUATION: - case Character.FINAL_QUOTE_PUNCTUATION: - case Character.INITIAL_QUOTE_PUNCTUATION: + case Character.CONNECTOR_PUNCTUATION: case Character.OTHER_PUNCTUATION: - case Character.START_PUNCTUATION: + case Character.INITIAL_QUOTE_PUNCTUATION: + case Character.FINAL_QUOTE_PUNCTUATION: + // General category "S" (symbol) + case Character.MATH_SYMBOL: + case Character.CURRENCY_SYMBOL: + case Character.MODIFIER_SYMBOL: + case Character.OTHER_SYMBOL: return true; default: switch (codePoint) { From 058e2f0ed4331553e44a77f348983ef7b4d524e4 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 21:05:47 +1100 Subject: [PATCH 066/345] HTML blocks: Add search, remove source --- .../src/main/java/org/commonmark/internal/HtmlBlockParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java index ce66c20da..123d9ec1f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java @@ -61,7 +61,7 @@ public class HtmlBlockParser extends AbstractBlockParser { "nav|noframes|" + "ol|optgroup|option|" + "p|param|" + - "section|source|summary|" + + "search|section|summary|" + "table|tbody|td|tfoot|th|thead|title|tr|track|" + "ul" + ")(?:\\s|[/]?[>]|$)", Pattern.CASE_INSENSITIVE), From 203eeb24c2ef84472fa8b795a29bf07e550b9d70 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 21:26:42 +1100 Subject: [PATCH 067/345] Update HTML comment scanning Looks like commonmark.js has a bug in its handling: https://github.com/commonmark/commonmark.js/issues/285 --- .../commonmark/internal/inline/HtmlInlineParser.java | 11 ++++++----- .../org/commonmark/test/HtmlInlineParserTest.java | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java index c85ae9d71..6dc525cb9 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -142,8 +142,9 @@ private static boolean tryProcessingInstruction(Scanner scanner) { } private static boolean tryComment(Scanner scanner) { - // spec: An HTML comment consists of , where text does not start with > or ->, does not end - // with -, and does not contain --. (See the HTML5 spec.) + // spec: An [HTML comment](@) consists of ``, ``, or ``, and `-->` (see the + // [HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)). // Skip first `-` scanner.next(); @@ -152,12 +153,12 @@ private static boolean tryComment(Scanner scanner) { } if (scanner.next('>') || scanner.next("->")) { - return false; + return true; } while (scanner.find('-') >= 0) { - if (scanner.next("--")) { - return scanner.next('>'); + if (scanner.next("-->")) { + return true; } else { scanner.next(); } diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java index 0172ca430..965a2f181 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java @@ -8,7 +8,11 @@ public class HtmlInlineParserTest extends CoreRenderingTestCase { public void comment() { assertRendering("inline ", "

    inline

    \n"); assertRendering("inline ", "

    inline

    \n"); - assertRendering("inline -->", "

    inline <!--->-->

    \n"); + assertRendering("inline ", "

    inline

    \n"); + assertRendering("inline ", "

    inline

    \n"); + assertRendering("inline ", "

    inline

    \n"); + assertRendering("inline -->", "

    inline -->

    \n"); + assertRendering("inline -->", "

    inline -->

    \n"); } @Test From a8af670b8ac5212b9a658b14c6a2a0d1170f83b7 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 2 Mar 2024 22:34:31 +1100 Subject: [PATCH 068/345] Update overrides in SpecIntegrationTest --- .../commonmark/integration/Extensions.java | 25 +++++++++++++ .../ExtensionsIntegrationTest.java | 37 +++++++++++++++++++ .../SourceSpanIntegrationTest.java | 2 +- .../integration/SpecIntegrationTest.java | 34 +++-------------- 4 files changed, 68 insertions(+), 30 deletions(-) create mode 100644 commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java create mode 100644 commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java new file mode 100644 index 000000000..5eddcc57a --- /dev/null +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java @@ -0,0 +1,25 @@ +package org.commonmark.integration; + +import org.commonmark.Extension; +import org.commonmark.ext.autolink.AutolinkExtension; +import org.commonmark.ext.front.matter.YamlFrontMatterExtension; +import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; +import org.commonmark.ext.gfm.tables.TablesExtension; +import org.commonmark.ext.image.attributes.ImageAttributesExtension; +import org.commonmark.ext.ins.InsExtension; +import org.commonmark.ext.task.list.items.TaskListItemsExtension; + +import java.util.Arrays; +import java.util.List; + +public class Extensions { + + static final List ALL_EXTENSIONS = Arrays.asList( + AutolinkExtension.create(), + ImageAttributesExtension.create(), + InsExtension.create(), + StrikethroughExtension.create(), + TablesExtension.create(), + TaskListItemsExtension.create(), + YamlFrontMatterExtension.create()); +} diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java new file mode 100644 index 000000000..f5d84b5a9 --- /dev/null +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java @@ -0,0 +1,37 @@ +package org.commonmark.integration; + +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; +import org.junit.Test; + +/** + * Tests to ensure all extensions work well together. + */ +public class ExtensionsIntegrationTest extends RenderingTestCase { + + protected static final Parser PARSER = Parser.builder() + .extensions(Extensions.ALL_EXTENSIONS) + .build(); + protected static final HtmlRenderer RENDERER = HtmlRenderer.builder() + .extensions(Extensions.ALL_EXTENSIONS) + .percentEncodeUrls(true) + .build(); + + @Test + public void testImageAttributes() { + assertRendering("![text](/url.png){height=5 width=6}", "

    \"text\"

    \n"); + } + + @Test + public void testTaskListItems() { + assertRendering("- [ ] task to do\n- [x] task done\n", + "
      \n
    • task to do
    • \n" + + "
    • task done
    • \n
    \n"); + + } + + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java index b6fa4922a..a0649f537 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java @@ -10,7 +10,7 @@ public class SourceSpanIntegrationTest extends SpecIntegrationTest { protected static final Parser PARSER = Parser.builder() - .extensions(EXTENSIONS) + .extensions(Extensions.ALL_EXTENSIONS) .includeSourceSpans(IncludeSourceSpans.BLOCKS) .build(); diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java index f434f65d2..2b615aa41 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java @@ -1,18 +1,9 @@ package org.commonmark.integration; -import org.commonmark.Extension; -import org.commonmark.ext.autolink.AutolinkExtension; -import org.commonmark.ext.image.attributes.ImageAttributesExtension; -import org.commonmark.ext.ins.InsExtension; -import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; -import org.commonmark.ext.gfm.tables.TablesExtension; -import org.commonmark.ext.front.matter.YamlFrontMatterExtension; -import org.commonmark.ext.task.list.items.TaskListItemsExtension; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.testutil.example.Example; import org.commonmark.testutil.SpecTestCase; -import org.junit.Assert; import org.junit.Test; import java.util.*; @@ -24,17 +15,9 @@ */ public class SpecIntegrationTest extends SpecTestCase { - protected static final List EXTENSIONS = Arrays.asList( - AutolinkExtension.create(), - ImageAttributesExtension.create(), - InsExtension.create(), - StrikethroughExtension.create(), - TablesExtension.create(), - TaskListItemsExtension.create(), - YamlFrontMatterExtension.create()); - protected static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + protected static final Parser PARSER = Parser.builder().extensions(Extensions.ALL_EXTENSIONS).build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. - protected static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).percentEncodeUrls(true).build(); + protected static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(Extensions.ALL_EXTENSIONS).percentEncodeUrls(true).build(); protected static final Map OVERRIDDEN_EXAMPLES = getOverriddenExamples(); public SpecIntegrationTest(Example example) { @@ -59,7 +42,7 @@ private static Map getOverriddenExamples() { Map m = new HashMap<>(); // Not a spec autolink because of space, but the resulting text contains a valid URL - m.put("\n", "

    <http://foo.bar/baz bim>

    \n"); + m.put("\n", "

    <https://foo.bar/baz bim>

    \n"); // Not a spec autolink, but the resulting text contains a valid email m.put("\n", "

    <foo+@bar.example.com>

    \n"); @@ -68,10 +51,10 @@ private static Map getOverriddenExamples() { m.put("\n", "

    <heck://bing.bong>

    \n"); // Not a spec autolink because of spaces, but autolink extension doesn't limit schemes - m.put("< http://foo.bar >\n", "

    < http://foo.bar >

    \n"); + m.put("< https://foo.bar >\n", "

    < https://foo.bar >

    \n"); // Plain autolink - m.put("http://example.com\n", "

    http://example.com

    \n"); + m.put("https://example.com\n", "

    https://example.com

    \n"); // Plain autolink m.put("foo@bar.example.com\n", "

    foo@bar.example.com

    \n"); @@ -80,13 +63,6 @@ private static Map getOverriddenExamples() { m.put("---\nFoo\n---\nBar\n---\nBaz\n", "

    Bar

    \n

    Baz

    \n"); m.put("---\n---\n", ""); - // Image attributes - m.put("![text](/url.png){height=5 width=6}", "\"text\""); - - // Task list items - m.put("- [ ] task to do\n- [x] task done\n", "
      \n
    • task to do
    • \n" + - "
    • task done
    • \n
    \n"); - return m; } From b13438921bf18c79e2cff3cf6c38a3aef0b73a8e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 9 Mar 2024 11:49:44 +1100 Subject: [PATCH 069/345] Extend Javadoc --- .../src/main/java/org/commonmark/ext/gfm/tables/TableCell.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java index 623e30062..57184ca38 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java @@ -34,7 +34,7 @@ public void setAlignment(Alignment alignment) { } /** - * @return the cell width + * @return the cell width (the number of dash and colon characters in the delimiter row of the table for this column) */ public int getWidth() { return width; From bd40c083a35f7d68d5eef9156ed5382bf40da5e0 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 12 Mar 2024 15:15:39 +1100 Subject: [PATCH 070/345] Save original literal in ThematicBreak on parsing Fixes #295. --- .../internal/ThematicBreakParser.java | 7 ++++- .../org/commonmark/node/ThematicBreak.java | 13 +++++++++ .../test/ThematicBreakParserTest.java | 29 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java index 88ad41ac9..0f0613221 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java @@ -8,6 +8,10 @@ public class ThematicBreakParser extends AbstractBlockParser { private final ThematicBreak block = new ThematicBreak(); + public ThematicBreakParser(String literal) { + block.setLiteral(literal); + } + @Override public Block getBlock() { return block; @@ -29,7 +33,8 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar int nextNonSpace = state.getNextNonSpaceIndex(); CharSequence line = state.getLine().getContent(); if (isThematicBreak(line, nextNonSpace)) { - return BlockStart.of(new ThematicBreakParser()).atIndex(line.length()); + var literal = String.valueOf(line.subSequence(state.getIndex(), line.length())); + return BlockStart.of(new ThematicBreakParser(literal)).atIndex(line.length()); } else { return BlockStart.none(); } diff --git a/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java b/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java index f81abaa31..836f8dfa1 100644 --- a/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java +++ b/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java @@ -2,8 +2,21 @@ public class ThematicBreak extends Block { + private String literal; + @Override public void accept(Visitor visitor) { visitor.visit(this); } + + /** + * @return the source literal that represents this node, if available + */ + public String getLiteral() { + return literal; + } + + public void setLiteral(String literal) { + this.literal = literal; + } } diff --git a/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java b/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java new file mode 100644 index 000000000..91ac76f67 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java @@ -0,0 +1,29 @@ +package org.commonmark.test; + +import org.commonmark.node.ThematicBreak; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class ThematicBreakParserTest { + + private static final Parser PARSER = Parser.builder().build(); + + @Test + public void testLiteral() { + assertLiteral("***", "***"); + assertLiteral("-- -", "-- -"); + assertLiteral(" __ __ __ ", " __ __ __ "); + assertLiteral("***", "> ***"); + } + + private static void assertLiteral(String expected, String input) { + var tb = Nodes.find(PARSER.parse(input), ThematicBreak.class); + assertNotNull(tb); + assertEquals(expected, tb.getLiteral()); + } +} From 4e65b065de6648c96051641b1f10ffdcaf19b95c Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 12 Mar 2024 16:27:19 +1100 Subject: [PATCH 071/345] Make Nodes.find throw when not found --- .../org/commonmark/test/ListBlockParserTest.java | 1 - .../src/test/java/org/commonmark/test/Nodes.java | 16 ++++++++++++++-- .../commonmark/test/ThematicBreakParserTest.java | 3 --- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java b/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java index a8a03fb74..2ba2116d4 100644 --- a/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java @@ -60,7 +60,6 @@ public void testOrderedListIndents() { private void assertListItemIndents(String input, int expectedMarkerIndent, int expectedContentIndent) { Node doc = PARSER.parse(input); ListItem listItem = Nodes.find(doc, ListItem.class); - assertNotNull(listItem); assertEquals(expectedMarkerIndent, listItem.getMarkerIndent()); assertEquals(expectedContentIndent, listItem.getContentIndent()); } diff --git a/commonmark/src/test/java/org/commonmark/test/Nodes.java b/commonmark/src/test/java/org/commonmark/test/Nodes.java index 25ce75836..8db504924 100644 --- a/commonmark/src/test/java/org/commonmark/test/Nodes.java +++ b/commonmark/src/test/java/org/commonmark/test/Nodes.java @@ -21,7 +21,7 @@ public static List getChildren(Node parent) { * @param parent The node to get children from (node itself will not be checked) * @param nodeClass The type of node to find */ - public static T find(Node parent, Class nodeClass) { + public static T tryFind(Node parent, Class nodeClass) { Node node = parent.getFirstChild(); while (node != null) { Node next = node.getNext(); @@ -29,7 +29,7 @@ public static T find(Node parent, Class nodeClass) { //noinspection unchecked return (T) node; } - T result = find(node, nodeClass); + T result = tryFind(node, nodeClass); if (result != null) { return result; } @@ -37,4 +37,16 @@ public static T find(Node parent, Class nodeClass) { } return null; } + + /** + * Recursively try to find a node with the given type within the children of the specified node. Throw if node + * could not be found. + */ + public static T find(Node parent, Class nodeClass) { + var node = tryFind(parent, nodeClass); + if (node == null) { + throw new IllegalArgumentException("Could not find a " + nodeClass.getSimpleName() + " node in " + parent); + } + return node; + } } diff --git a/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java b/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java index 91ac76f67..a6b297f8e 100644 --- a/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java @@ -2,8 +2,6 @@ import org.commonmark.node.ThematicBreak; import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -23,7 +21,6 @@ public void testLiteral() { private static void assertLiteral(String expected, String input) { var tb = Nodes.find(PARSER.parse(input), ThematicBreak.class); - assertNotNull(tb); assertEquals(expected, tb.getLiteral()); } } From 21fc1df7adfc6e8d3e876e56702a0adb81397a27 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 12 Mar 2024 16:28:13 +1100 Subject: [PATCH 072/345] Fix LinkReferenceDefinition having null SourceSpan when title is present Fixes #292. --- .../LinkReferenceDefinitionParser.java | 6 ++++-- .../commonmark/parser/block/BlockParser.java | 4 ++++ .../org/commonmark/test/SourceSpansTest.java | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index d11a6f228..ebe6ebb23 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -100,6 +100,10 @@ State getState() { } private boolean startDefinition(Scanner scanner) { + // Finish any outstanding references now. We don't do this earlier because we need addSourceSpan to have been + // called before we do it. + finishReference(); + scanner.whitespace(); if (!scanner.next('[')) { return false; @@ -205,7 +209,6 @@ private boolean startTitle(Scanner scanner) { title.append('\n'); } } else { - finishReference(); // There might be another reference instead, try that for the same character. state = State.START_DEFINITION; } @@ -235,7 +238,6 @@ private boolean title(Scanner scanner) { return false; } referenceValid = true; - finishReference(); paragraphLines.clear(); // See if there's another definition. diff --git a/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java b/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java index aa956a48a..addd90d1a 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java @@ -34,6 +34,10 @@ public interface BlockParser { BlockContinue tryContinue(ParserState parserState); + /** + * Add the part of a line that belongs to this block parser to parse (i.e. without any container block markers). + * Note that the line will only include a {@link SourceLine#getSourceSpan()} if source spans are enabled for inlines. + */ void addLine(SourceLine line); /** diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index a96d9c58b..959598935 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -8,6 +8,7 @@ import java.util.ArrayDeque; import java.util.Arrays; import java.util.Deque; +import java.util.List; import static org.junit.Assert.assertEquals; @@ -164,6 +165,24 @@ public void linkReferenceDefinition() { assertEquals(Arrays.asList(SourceSpan.of(1, 0, 4)), paragraph.getSourceSpans()); } + @Test + public void linkReferenceDefinitionMultiple() { + var doc = PARSER.parse("[foo]: /foo\n[bar]: /bar\n"); + var def1 = (LinkReferenceDefinition) doc.getFirstChild(); + var def2 = (LinkReferenceDefinition) doc.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 0, 11)), def1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(1, 0, 11)), def2.getSourceSpans()); + } + + @Test + public void linkReferenceDefinitionWithTitle() { + var doc = PARSER.parse("[1]: #not-code \"Text\"\n[foo]: /foo\n"); + var def1 = (LinkReferenceDefinition) doc.getFirstChild(); + var def2 = (LinkReferenceDefinition) doc.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 0, 21)), def1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(1, 0, 11)), def2.getSourceSpans()); + } + @Test public void linkReferenceDefinitionHeading() { // This is probably the trickiest because we have a link reference definition at the start of a paragraph From 16c6ff26b1b62f40a394ec8578a2a54e7717aecb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 14 Mar 2024 11:43:04 +1100 Subject: [PATCH 073/345] Fix markdown renderer for manually created list nodes --- .../commonmark/internal/ListBlockParser.java | 18 +++---- .../renderer/text/BulletListHolder.java | 6 +-- .../renderer/text/OrderedListHolder.java | 8 +-- .../java/org/commonmark/node/BulletList.java | 26 ++++++++-- .../java/org/commonmark/node/ListItem.java | 19 +++---- .../java/org/commonmark/node/OrderedList.java | 51 ++++++++++++++++--- .../renderer/html/CoreHtmlNodeRenderer.java | 2 +- .../markdown/CoreMarkdownNodeRenderer.java | 39 +++++++------- .../markdown/MarkdownRendererTest.java | 33 ++++++++++++ .../commonmark/test/ListBlockParserTest.java | 4 +- 10 files changed, 146 insertions(+), 60 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index 0ff644a47..d77744da7 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -4,6 +4,8 @@ import org.commonmark.node.*; import org.commonmark.parser.block.*; +import java.util.Objects; + public class ListBlockParser extends AbstractBlockParser { private final ListBlock block; @@ -90,7 +92,7 @@ private static ListData parseList(CharSequence line, final int markerIndex, fina if (inParagraph) { // If the list item is ordered, the start number must be 1 to interrupt a paragraph. - if (listBlock instanceof OrderedList && ((OrderedList) listBlock).getStartNumber() != 1) { + if (listBlock instanceof OrderedList && ((OrderedList) listBlock).getMarkerStartNumber() != 1) { return null; } // Empty list item can not interrupt a paragraph. @@ -116,7 +118,7 @@ private static ListMarkerData parseListMarker(CharSequence line, int index) { case '*': if (isSpaceTabOrEnd(line, index + 1)) { BulletList bulletList = new BulletList(); - bulletList.setBulletMarker(c); + bulletList.setMarker(String.valueOf(c)); return new ListMarkerData(bulletList, index + 1); } else { return null; @@ -154,8 +156,8 @@ private static ListMarkerData parseOrderedList(CharSequence line, int index) { if (digits >= 1 && isSpaceTabOrEnd(line, i + 1)) { String number = line.subSequence(index, i).toString(); OrderedList orderedList = new OrderedList(); - orderedList.setStartNumber(Integer.parseInt(number)); - orderedList.setDelimiter(c); + orderedList.setMarkerStartNumber(Integer.parseInt(number)); + orderedList.setMarkerDelimiter(String.valueOf(c)); return new ListMarkerData(orderedList, i + 1); } else { return null; @@ -188,17 +190,13 @@ private static boolean isSpaceTabOrEnd(CharSequence line, int index) { */ private static boolean listsMatch(ListBlock a, ListBlock b) { if (a instanceof BulletList && b instanceof BulletList) { - return equals(((BulletList) a).getBulletMarker(), ((BulletList) b).getBulletMarker()); + return Objects.equals(((BulletList) a).getMarker(), ((BulletList) b).getMarker()); } else if (a instanceof OrderedList && b instanceof OrderedList) { - return equals(((OrderedList) a).getDelimiter(), ((OrderedList) b).getDelimiter()); + return Objects.equals(((OrderedList) a).getMarkerDelimiter(), ((OrderedList) b).getMarkerDelimiter()); } return false; } - private static boolean equals(Object a, Object b) { - return (a == null) ? (b == null) : a.equals(b); - } - public static class Factory extends AbstractBlockParserFactory { @Override diff --git a/commonmark/src/main/java/org/commonmark/internal/renderer/text/BulletListHolder.java b/commonmark/src/main/java/org/commonmark/internal/renderer/text/BulletListHolder.java index f08ccebd6..a9271dcdb 100644 --- a/commonmark/src/main/java/org/commonmark/internal/renderer/text/BulletListHolder.java +++ b/commonmark/src/main/java/org/commonmark/internal/renderer/text/BulletListHolder.java @@ -3,14 +3,14 @@ import org.commonmark.node.BulletList; public class BulletListHolder extends ListHolder { - private final char marker; + private final String marker; public BulletListHolder(ListHolder parent, BulletList list) { super(parent); - marker = list.getBulletMarker(); + marker = list.getMarker(); } - public char getMarker() { + public String getMarker() { return marker; } } diff --git a/commonmark/src/main/java/org/commonmark/internal/renderer/text/OrderedListHolder.java b/commonmark/src/main/java/org/commonmark/internal/renderer/text/OrderedListHolder.java index e02ecea7c..e5e470951 100644 --- a/commonmark/src/main/java/org/commonmark/internal/renderer/text/OrderedListHolder.java +++ b/commonmark/src/main/java/org/commonmark/internal/renderer/text/OrderedListHolder.java @@ -3,16 +3,16 @@ import org.commonmark.node.OrderedList; public class OrderedListHolder extends ListHolder { - private final char delimiter; + private final String delimiter; private int counter; public OrderedListHolder(ListHolder parent, OrderedList list) { super(parent); - delimiter = list.getDelimiter(); - counter = list.getStartNumber(); + delimiter = list.getMarkerDelimiter() != null ? list.getMarkerDelimiter() : "."; + counter = list.getMarkerStartNumber() != null ? list.getMarkerStartNumber() : 1; } - public char getDelimiter() { + public String getDelimiter() { return delimiter; } diff --git a/commonmark/src/main/java/org/commonmark/node/BulletList.java b/commonmark/src/main/java/org/commonmark/node/BulletList.java index 127862312..4d5c2a894 100644 --- a/commonmark/src/main/java/org/commonmark/node/BulletList.java +++ b/commonmark/src/main/java/org/commonmark/node/BulletList.java @@ -2,19 +2,37 @@ public class BulletList extends ListBlock { - private char bulletMarker; + private String marker; @Override public void accept(Visitor visitor) { visitor.visit(this); } + /** + * @return the bullet list marker that was used, e.g. {@code -}, {@code *} or {@code +}, if available, or null otherwise + */ + public String getMarker() { + return marker; + } + + public void setMarker(String marker) { + this.marker = marker; + } + + /** + * @deprecated use {@link #getMarker()} instead + */ + @Deprecated public char getBulletMarker() { - return bulletMarker; + return marker != null && !marker.isEmpty() ? marker.charAt(0) : '\0'; } + /** + * @deprecated use {@link #getMarker()} instead + */ + @Deprecated public void setBulletMarker(char bulletMarker) { - this.bulletMarker = bulletMarker; + this.marker = bulletMarker != '\0' ? String.valueOf(bulletMarker) : null; } - } diff --git a/commonmark/src/main/java/org/commonmark/node/ListItem.java b/commonmark/src/main/java/org/commonmark/node/ListItem.java index 21f4e2b82..4e63b6145 100644 --- a/commonmark/src/main/java/org/commonmark/node/ListItem.java +++ b/commonmark/src/main/java/org/commonmark/node/ListItem.java @@ -2,8 +2,8 @@ public class ListItem extends Block { - private int markerIndent; - private int contentIndent; + private Integer markerIndent; + private Integer contentIndent; @Override public void accept(Visitor visitor) { @@ -11,7 +11,8 @@ public void accept(Visitor visitor) { } /** - * Returns the indent of the marker such as "-" or "1." in columns (spaces or tab stop of 4). + * Returns the indent of the marker such as "-" or "1." in columns (spaces or tab stop of 4) if available, or null + * otherwise. *

    * Some examples and their marker indent: *

    - Foo
    @@ -21,17 +22,17 @@ public void accept(Visitor visitor) { *
      1. Foo
    * Marker indent: 2 */ - public int getMarkerIndent() { + public Integer getMarkerIndent() { return markerIndent; } - public void setMarkerIndent(int markerIndent) { + public void setMarkerIndent(Integer markerIndent) { this.markerIndent = markerIndent; } /** - * Returns the indent of the content in columns (spaces or tab stop of 4). The content indent is counted from the - * beginning of the line and includes the marker on the first line. + * Returns the indent of the content in columns (spaces or tab stop of 4) if available, or null otherwise. + * The content indent is counted from the beginning of the line and includes the marker on the first line. *

    * Some examples and their content indent: *

    - Foo
    @@ -44,11 +45,11 @@ public void setMarkerIndent(int markerIndent) { * Note that subsequent lines in the same list item need to be indented by at least the content indent to be counted * as part of the list item. */ - public int getContentIndent() { + public Integer getContentIndent() { return contentIndent; } - public void setContentIndent(int contentIndent) { + public void setContentIndent(Integer contentIndent) { this.contentIndent = contentIndent; } } diff --git a/commonmark/src/main/java/org/commonmark/node/OrderedList.java b/commonmark/src/main/java/org/commonmark/node/OrderedList.java index 1f988234c..0bbe09917 100644 --- a/commonmark/src/main/java/org/commonmark/node/OrderedList.java +++ b/commonmark/src/main/java/org/commonmark/node/OrderedList.java @@ -2,28 +2,65 @@ public class OrderedList extends ListBlock { - private int startNumber; - private char delimiter; + private String markerDelimiter; + private Integer markerStartNumber; @Override public void accept(Visitor visitor) { visitor.visit(this); } + /** + * @return the start number used in the marker, e.g. {@code 1}, if available, or null otherwise + */ + public Integer getMarkerStartNumber() { + return markerStartNumber; + } + + public void setMarkerStartNumber(Integer markerStartNumber) { + this.markerStartNumber = markerStartNumber; + } + + /** + * @return the delimiter used in the marker, e.g. {@code .} or {@code )}, if available, or null otherwise + */ + public String getMarkerDelimiter() { + return markerDelimiter; + } + + public void setMarkerDelimiter(String markerDelimiter) { + this.markerDelimiter = markerDelimiter; + } + + /** + * @deprecated use {@link #getMarkerStartNumber()} instead + */ + @Deprecated public int getStartNumber() { - return startNumber; + return markerStartNumber != null ? markerStartNumber : 0; } + /** + * @deprecated use {@link #setMarkerStartNumber} instead + */ + @Deprecated public void setStartNumber(int startNumber) { - this.startNumber = startNumber; + this.markerStartNumber = startNumber != 0 ? startNumber : null; } + /** + * @deprecated use {@link #getMarkerDelimiter()} instead + */ + @Deprecated public char getDelimiter() { - return delimiter; + return markerDelimiter != null && !markerDelimiter.isEmpty() ? markerDelimiter.charAt(0) : '\0'; } + /** + * @deprecated use {@link #setMarkerDelimiter} instead + */ + @Deprecated public void setDelimiter(char delimiter) { - this.delimiter = delimiter; + this.markerDelimiter = delimiter != '\0' ? String.valueOf(delimiter) : null; } - } diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java index 7d3552668..47343b53c 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -168,7 +168,7 @@ public void visit(ListItem listItem) { @Override public void visit(OrderedList orderedList) { - int start = orderedList.getStartNumber(); + int start = orderedList.getMarkerStartNumber() != null ? orderedList.getMarkerStartNumber() : 1; Map attrs = new LinkedHashMap<>(); if (start != 1) { attrs.put("start", String.valueOf(start)); diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index e0cc4eb25..d5770155a 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -227,33 +227,32 @@ public void visit(OrderedList orderedList) { @Override public void visit(ListItem listItem) { - int contentIndent = listItem.getContentIndent(); - boolean pushedPrefix = false; + int markerIndent = listItem.getMarkerIndent() != null ? listItem.getMarkerIndent() : 0; + String marker; if (listHolder instanceof BulletListHolder) { BulletListHolder bulletListHolder = (BulletListHolder) listHolder; - String marker = repeat(" ", listItem.getMarkerIndent()) + bulletListHolder.bulletMarker; - writer.writePrefix(marker); - writer.writePrefix(repeat(" ", contentIndent - marker.length())); - writer.pushPrefix(repeat(" ", contentIndent)); - pushedPrefix = true; + marker = repeat(" ", markerIndent) + bulletListHolder.marker; } else if (listHolder instanceof OrderedListHolder) { OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; - String marker = repeat(" ", listItem.getMarkerIndent()) + orderedListHolder.number + orderedListHolder.delimiter; + marker = repeat(" ", markerIndent) + orderedListHolder.number + orderedListHolder.delimiter; orderedListHolder.number++; - writer.writePrefix(marker); - writer.writePrefix(repeat(" ", contentIndent - marker.length())); - writer.pushPrefix(repeat(" ", contentIndent)); - pushedPrefix = true; + } else { + throw new IllegalStateException("Unknown list holder type: " + listHolder); } + Integer contentIndent = listItem.getContentIndent(); + String spaces = contentIndent != null ? repeat(" ", contentIndent - marker.length()) : " "; + writer.writePrefix(marker); + writer.writePrefix(spaces); + writer.pushPrefix(repeat(" ", marker.length() + spaces.length())); + if (listItem.getFirstChild() == null) { // Empty list item writer.block(); } else { visitChildren(listItem); } - if (pushedPrefix) { - writer.popPrefix(); - } + + writer.popPrefix(); } @Override @@ -489,22 +488,22 @@ protected ListHolder(ListHolder parent) { } private static class BulletListHolder extends ListHolder { - final char bulletMarker; + final String marker; public BulletListHolder(ListHolder parent, BulletList bulletList) { super(parent); - this.bulletMarker = bulletList.getBulletMarker(); + this.marker = bulletList.getMarker() != null ? bulletList.getMarker() : "-"; } } private static class OrderedListHolder extends ListHolder { - final char delimiter; + final String delimiter; private int number; protected OrderedListHolder(ListHolder parent, OrderedList orderedList) { super(parent); - delimiter = orderedList.getDelimiter(); - number = orderedList.getStartNumber(); + delimiter = orderedList.getMarkerDelimiter() != null ? orderedList.getMarkerDelimiter() : "."; + number = orderedList.getMarkerStartNumber() != null ? orderedList.getMarkerStartNumber() : 1; } } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 9bab92bcc..522b16cd4 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -2,8 +2,10 @@ import org.commonmark.node.*; import org.commonmark.parser.Parser; +import org.commonmark.testutil.Asserts; import org.junit.Test; +import static org.commonmark.testutil.Asserts.assertRendering; import static org.junit.Assert.assertEquals; public class MarkdownRendererTest { @@ -103,6 +105,21 @@ public void testBulletListItems() { assertRoundTrip("- \n\nFoo\n"); } + @Test + public void testBulletListItemsFromAst() { + var doc = new Document(); + var list = new BulletList(); + var item = new ListItem(); + item.appendChild(new Text("Test")); + list.appendChild(item); + doc.appendChild(list); + + assertRendering("", "- Test\n", render(doc)); + + list.setMarker("*"); + assertRendering("", "* Test\n", render(doc)); + } + @Test public void testOrderedListItems() { assertRoundTrip("1. foo\n"); @@ -116,6 +133,22 @@ public void testOrderedListItems() { assertRoundTrip(" 1. one\n\n two\n"); } + @Test + public void testOrderedListItemsFromAst() { + var doc = new Document(); + var list = new OrderedList(); + var item = new ListItem(); + item.appendChild(new Text("Test")); + list.appendChild(item); + doc.appendChild(list); + + assertRendering("", "1. Test\n", render(doc)); + + list.setMarkerStartNumber(2); + list.setMarkerDelimiter(")"); + assertRendering("", "2) Test\n", render(doc)); + } + // Inlines @Test diff --git a/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java b/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java index 2ba2116d4..667d60efe 100644 --- a/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java @@ -60,7 +60,7 @@ public void testOrderedListIndents() { private void assertListItemIndents(String input, int expectedMarkerIndent, int expectedContentIndent) { Node doc = PARSER.parse(input); ListItem listItem = Nodes.find(doc, ListItem.class); - assertEquals(expectedMarkerIndent, listItem.getMarkerIndent()); - assertEquals(expectedContentIndent, listItem.getContentIndent()); + assertEquals(expectedMarkerIndent, (int) listItem.getMarkerIndent()); + assertEquals(expectedContentIndent, (int) listItem.getContentIndent()); } } From 8b7f928e741d20062b0f07e9c0e9e9d4bd98487e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 14 Mar 2024 17:02:12 +1100 Subject: [PATCH 074/345] Fix markdown renderer for manually created fenced code blocks --- .../internal/FencedCodeBlockParser.java | 24 +++-- .../org/commonmark/node/FencedCodeBlock.java | 88 ++++++++++++++++--- .../markdown/CoreMarkdownNodeRenderer.java | 58 +++++++----- .../markdown/MarkdownRendererTest.java | 17 ++++ 4 files changed, 148 insertions(+), 39 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java index a16758dd4..d550f1d25 100644 --- a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java @@ -12,13 +12,17 @@ public class FencedCodeBlockParser extends AbstractBlockParser { private final FencedCodeBlock block = new FencedCodeBlock(); + private final char fenceChar; + private final int openingFenceLength; private String firstLine; private StringBuilder otherLines = new StringBuilder(); public FencedCodeBlockParser(char fenceChar, int fenceLength, int fenceIndent) { - block.setFenceChar(fenceChar); - block.setFenceLength(fenceLength); + this.fenceChar = fenceChar; + this.openingFenceLength = fenceLength; + block.setFenceCharacter(String.valueOf(fenceChar)); + block.setOpeningFenceLength(fenceLength); block.setFenceIndent(fenceIndent); } @@ -32,7 +36,7 @@ public BlockContinue tryContinue(ParserState state) { int nextNonSpace = state.getNextNonSpaceIndex(); int newIndex = state.getIndex(); CharSequence line = state.getLine().getContent(); - if (state.getIndent() < Parsing.CODE_BLOCK_INDENT && nextNonSpace < line.length() && line.charAt(nextNonSpace) == block.getFenceChar() && isClosing(line, nextNonSpace)) { + if (state.getIndent() < Parsing.CODE_BLOCK_INDENT && nextNonSpace < line.length() && tryClosing(line, nextNonSpace)) { // closing fence - we're at end of line, so we can finalize now return BlockContinue.finished(); } else { @@ -76,7 +80,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar int nextNonSpace = state.getNextNonSpaceIndex(); FencedCodeBlockParser blockParser = checkOpener(state.getLine().getContent(), nextNonSpace, indent); if (blockParser != null) { - return BlockStart.of(blockParser).atIndex(nextNonSpace + blockParser.block.getFenceLength()); + return BlockStart.of(blockParser).atIndex(nextNonSpace + blockParser.block.getOpeningFenceLength()); } else { return BlockStart.none(); } @@ -119,15 +123,17 @@ private static FencedCodeBlockParser checkOpener(CharSequence line, int index, i // spec: The content of the code block consists of all subsequent lines, until a closing code fence of the same type // as the code block began with (backticks or tildes), and with at least as many backticks or tildes as the opening // code fence. - private boolean isClosing(CharSequence line, int index) { - char fenceChar = block.getFenceChar(); - int fenceLength = block.getFenceLength(); + private boolean tryClosing(CharSequence line, int index) { int fences = Characters.skip(fenceChar, line, index, line.length()) - index; - if (fences < fenceLength) { + if (fences < openingFenceLength) { return false; } // spec: The closing code fence [...] may be followed only by spaces, which are ignored. int after = Characters.skipSpaceTab(line, index + fences, line.length()); - return after == line.length(); + if (after == line.length()) { + block.setClosingFenceLength(fences); + return true; + } + return false; } } diff --git a/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java b/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java index 7e2612331..205ef9126 100644 --- a/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java @@ -2,8 +2,9 @@ public class FencedCodeBlock extends Block { - private char fenceChar; - private int fenceLength; + private String fenceCharacter; + private Integer openingFenceLength; + private Integer closingFenceLength; private int fenceIndent; private String info; @@ -14,20 +15,47 @@ public void accept(Visitor visitor) { visitor.visit(this); } - public char getFenceChar() { - return fenceChar; + /** + * @return the fence character that was used, e.g. {@code `} or {@code ~}, if available, or null otherwise + */ + public String getFenceCharacter() { + return fenceCharacter; } - public void setFenceChar(char fenceChar) { - this.fenceChar = fenceChar; + public void setFenceCharacter(String fenceCharacter) { + this.fenceCharacter = fenceCharacter; } - public int getFenceLength() { - return fenceLength; + /** + * @return the length of the opening fence (how many of {{@link #getFenceCharacter()}} were used to start the code + * block) if available, or null otherwise + */ + public Integer getOpeningFenceLength() { + return openingFenceLength; } - public void setFenceLength(int fenceLength) { - this.fenceLength = fenceLength; + public void setOpeningFenceLength(Integer openingFenceLength) { + if (openingFenceLength != null && openingFenceLength < 3) { + throw new IllegalArgumentException("openingFenceLength needs to be >= 3"); + } + checkFenceLengths(openingFenceLength, closingFenceLength); + this.openingFenceLength = openingFenceLength; + } + + /** + * @return the length of the closing fence (how many of {@link #getFenceCharacter()} were used to end the code + * block) if available, or null otherwise + */ + public Integer getClosingFenceLength() { + return closingFenceLength; + } + + public void setClosingFenceLength(Integer closingFenceLength) { + if (closingFenceLength != null && closingFenceLength < 3) { + throw new IllegalArgumentException("closingFenceLength needs to be >= 3"); + } + checkFenceLengths(openingFenceLength, closingFenceLength); + this.closingFenceLength = closingFenceLength; } public int getFenceIndent() { @@ -56,4 +84,44 @@ public String getLiteral() { public void setLiteral(String literal) { this.literal = literal; } + + /** + * @deprecated use {@link #getFenceCharacter()} instead + */ + @Deprecated + public char getFenceChar() { + return fenceCharacter != null && !fenceCharacter.isEmpty() ? fenceCharacter.charAt(0) : '\0'; + } + + /** + * @deprecated use {@link #setFenceCharacter} instead + */ + @Deprecated + public void setFenceChar(char fenceChar) { + this.fenceCharacter = fenceChar != '\0' ? String.valueOf(fenceChar) : null; + } + + /** + * @deprecated use {@link #getOpeningFenceLength} instead + */ + @Deprecated + public int getFenceLength() { + return openingFenceLength != null ? openingFenceLength : 0; + } + + /** + * @deprecated use {@link #setOpeningFenceLength} instead + */ + @Deprecated + public void setFenceLength(int fenceLength) { + this.openingFenceLength = fenceLength != 0 ? fenceLength : null; + } + + private static void checkFenceLengths(Integer openingFenceLength, Integer closingFenceLength) { + if (openingFenceLength != null && closingFenceLength != null) { + if (closingFenceLength < openingFenceLength) { + throw new IllegalArgumentException("fence lengths required to be: closingFenceLength >= openingFenceLength"); + } + } + } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index d5770155a..229d9d262 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -147,10 +147,25 @@ public void visit(IndentedCodeBlock indentedCodeBlock) { } @Override - public void visit(FencedCodeBlock fencedCodeBlock) { - String literal = fencedCodeBlock.getLiteral(); - String fence = repeat(String.valueOf(fencedCodeBlock.getFenceChar()), fencedCodeBlock.getFenceLength()); - int indent = fencedCodeBlock.getFenceIndent(); + public void visit(FencedCodeBlock codeBlock) { + String literal = codeBlock.getLiteral(); + String fenceChar = codeBlock.getFenceCharacter() != null ? codeBlock.getFenceCharacter() : "`"; + int openingFenceLength; + if (codeBlock.getOpeningFenceLength() != null) { + // If we have a known fence length, use it + openingFenceLength = codeBlock.getOpeningFenceLength(); + } else { + // Otherwise, calculate the closing fence length pessimistically, e.g. if the code block itself contains a + // line with ```, we need to use a fence of length 4. If ``` occurs with non-whitespace characters on a + // line, we technically don't need a longer fence, but it's not incorrect to do so. + int fenceCharsInLiteral = findMaxRunLength(fenceChar, literal); + openingFenceLength = Math.max(fenceCharsInLiteral + 1, 3); + } + int closingFenceLength = codeBlock.getClosingFenceLength() != null ? codeBlock.getClosingFenceLength() : openingFenceLength; + + String openingFence = repeat(fenceChar, openingFenceLength); + String closingFence = repeat(fenceChar, closingFenceLength); + int indent = codeBlock.getFenceIndent(); if (indent > 0) { String indentPrefix = repeat(" ", indent); @@ -158,9 +173,9 @@ public void visit(FencedCodeBlock fencedCodeBlock) { writer.pushPrefix(indentPrefix); } - writer.raw(fence); - if (fencedCodeBlock.getInfo() != null) { - writer.raw(fencedCodeBlock.getInfo()); + writer.raw(openingFence); + if (codeBlock.getInfo() != null) { + writer.raw(codeBlock.getInfo()); } writer.line(); if (!literal.isEmpty()) { @@ -170,7 +185,7 @@ public void visit(FencedCodeBlock fencedCodeBlock) { writer.line(); } } - writer.raw(fence); + writer.raw(closingFence); if (indent > 0) { writer.popPrefix(); } @@ -259,7 +274,7 @@ public void visit(ListItem listItem) { public void visit(Code code) { String literal = code.getLiteral(); // If the literal includes backticks, we can surround them by using one more backtick. - int backticks = findMaxRunLength('`', literal); + int backticks = findMaxRunLength("`", literal); for (int i = 0; i < backticks + 1; i++) { writer.raw('`'); } @@ -411,19 +426,22 @@ protected void visitChildren(Node parent) { } } - private static int findMaxRunLength(char c, CharSequence s) { - int backticks = 0; - int start = 0; - while (start < s.length()) { - int index = Characters.find(c, s, start); - if (index != -1) { - start = Characters.skip(c, s, index + 1, s.length()); - backticks = Math.max(backticks, start - index); - } else { - break; + private static int findMaxRunLength(String needle, String s) { + int maxRunLength = 0; + int pos = 0; + while (pos < s.length()) { + pos = s.indexOf(needle, pos); + if (pos == -1) { + return maxRunLength; } + int runLength = 0; + do { + pos += needle.length(); + runLength++; + } while (s.startsWith(needle, pos)); + maxRunLength = Math.max(runLength, maxRunLength); } - return backticks; + return maxRunLength; } private static boolean contains(String s, CharMatcher charMatcher) { diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 522b16cd4..91af1bfe8 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -52,6 +52,23 @@ public void testFencedCodeBlocks() { assertRoundTrip("```info\ntest\n```\n"); assertRoundTrip(" ```\n test\n ```\n"); assertRoundTrip("```\n```\n"); + + // Preserve the length + assertRoundTrip("````\ntest\n````\n"); + assertRoundTrip("~~~\ntest\n~~~~~~\n"); + } + + @Test + public void testFencedCodeBlocksFromAst() { + var doc = new Document(); + var codeBlock = new FencedCodeBlock(); + codeBlock.setLiteral("hi code"); + doc.appendChild(codeBlock); + + assertRendering("", "```\nhi code\n```\n", render(doc)); + + codeBlock.setLiteral("hi`\n```\n``test"); + assertRendering("", "````\nhi`\n```\n``test\n````\n", render(doc)); } @Test From c5fe4195e5af114b6d38b9809160a8c82e1e98e1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 15 Mar 2024 16:47:34 +1100 Subject: [PATCH 075/345] Prepare CHANGELOG for release 0.22 --- CHANGELOG.md | 38 +++++++++++++++---- .../renderer/markdown/MarkdownRenderer.java | 1 + 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52502922b..e31b8d15a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,37 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. -## [Unreleased] -### Changed -- Modular JAR: Require at least Java 11 and add a module descriptor (module-info), - remove no longer necessary `Automatic-Module-Name` header +## [0.22.0] - 2024-03-15 +### Added +- New `MarkdownRenderer` for rendering nodes to Markdown (CommonMark)! + Note that while care is taken to produce equivalent Markdown, some differences + in the original Markdown (if parsed) are not preserved, such as: + - The type of heading used + - The type of link used (reference links will be rendered as inline links) + - Whether special characters are escaped or not + - Leading and trailing whitespace +- Modular JAR (JPMS): All artifacts now include module descriptors (module-info) + so jlink can be used; the old `Automatic-Module-Name` manifest entries were removed - New package `org.commonmark.parser.beta` containing classes that are not part of - the stable API but are exported from the module (because they might be useful for - extension parsers). + the stable API but are exported from the module because they might be useful for + extension parsers +- New package `org.commonmark.text` for text related utilities that are useful for + both parsing and rendering +- `TableCell` now has `getWidth` returning the number of dash and colon characters + in the delimiter row, useful for rendering proportional width tables (#296) +- `ThematicBreak` now has `getLiteral` containing the string that was used in the + source when parsing (#309) +- `ListItem` now has `getMarkerIndent` and `getContentIndent` for retrieving the + space between the start of the line and the marker/content +- Deprecated a some properties of `BulletList`, `OrderedList`, `FencedCodeBlock` + and replaced with nullable ones because they might not be set when constructing + these nodes manually instead of via parsing +### Changed +- Java 11 or later is now required (dropping support for Java 8) +- Update to CommonMark spec 0.31.2 +### Fixed +- Fix `LinkReferenceDefinition` having null `SourceSpan` when title is present + and parsing with source spans option enabled (#310) ## [0.21.0] - 2022-11-17 ### Added @@ -387,7 +411,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. -[Unreleased]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.21.0...HEAD +[0.22.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.21.0...commonmark-parent-0.22.0 [0.21.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.20.0...commonmark-parent-0.21.0 [0.20.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.19.0...commonmark-parent-0.20.0 [0.19.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.2...commonmark-parent-0.19.0 diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java index 25fa9a142..2ee89ea1a 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java @@ -14,6 +14,7 @@ * Note that it doesn't currently preserve the exact syntax of the original input Markdown (if any): *
      *
    • Headings are output as ATX headings if possible (multi-line headings need Setext headings)
    • + *
    • Links are always rendered as inline links (no support for reference links yet)
    • *
    • Escaping might be over-eager, e.g. a plain {@code *} might be escaped * even though it doesn't need to be in that particular context
    • *
    • Leading whitespace in paragraphs is not preserved
    • From bd4012e857618f929efaff5b715038e9fcbd5c0c Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 15 Mar 2024 16:52:10 +1100 Subject: [PATCH 076/345] Prepare for version 0.22.0 Set the version using `mvn versions:set -DnewVersion=0.22.0-SNAPSHOT` --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 22 +++++++++++----------- 12 files changed, 22 insertions(+), 22 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 2f70f7a0e..44cbe413d 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index b452073ab..e984528fb 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 01fe2b23c..14d46f4b5 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 330490ea6..035dde70d 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 6fbec9007..b5ca6069f 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 68e5f627b..0d1581b2a 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 3e3bebf32..6e44d5141 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 97bdc5c48..979533249 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index da5146a15..5eb9c4f27 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index ce332e6c2..68f1f1c96 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 524ba9c42..369d4eb24 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 4eac3b7b2..2def77372 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -111,52 +111,52 @@ org.commonmark commonmark - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT org.commonmark commonmark-ext-ins - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT org.commonmark commonmark-test-util - 0.21.1-SNAPSHOT + 0.22.0-SNAPSHOT From 913d4386c5dbccbacabee8458a0098d53d5f00f2 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Fri, 15 Mar 2024 11:47:03 +0000 Subject: [PATCH 077/345] [maven-release-plugin] prepare release commonmark-parent-0.22.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 24 ++++++++++++------------ 12 files changed, 23 insertions(+), 23 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 44cbe413d..0d9f6098e 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0-SNAPSHOT + 0.22.0 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index e984528fb..0cd3dd435 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0-SNAPSHOT + 0.22.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 14d46f4b5..0a0873667 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0-SNAPSHOT + 0.22.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 035dde70d..0238d40cb 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0-SNAPSHOT + 0.22.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index b5ca6069f..21c8c43c8 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0-SNAPSHOT + 0.22.0 commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 0d1581b2a..3c49491d9 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0-SNAPSHOT + 0.22.0 commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 6e44d5141..15e2bc0a5 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0-SNAPSHOT + 0.22.0 commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 979533249..d0acc4176 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.22.0-SNAPSHOT + 0.22.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 5eb9c4f27..9558a65b8 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0-SNAPSHOT + 0.22.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 68f1f1c96..58a8c94ac 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0-SNAPSHOT + 0.22.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 369d4eb24..c6501f806 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0-SNAPSHOT + 0.22.0 commonmark diff --git a/pom.xml b/pom.xml index 2def77372..693760923 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.22.0-SNAPSHOT + 0.22.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -111,52 +111,52 @@ org.commonmark commonmark - 0.22.0-SNAPSHOT + 0.22.0 org.commonmark commonmark-ext-autolink - 0.22.0-SNAPSHOT + 0.22.0 org.commonmark commonmark-ext-image-attributes - 0.22.0-SNAPSHOT + 0.22.0 org.commonmark commonmark-ext-ins - 0.22.0-SNAPSHOT + 0.22.0 org.commonmark commonmark-ext-gfm-strikethrough - 0.22.0-SNAPSHOT + 0.22.0 org.commonmark commonmark-ext-gfm-tables - 0.22.0-SNAPSHOT + 0.22.0 org.commonmark commonmark-ext-heading-anchor - 0.22.0-SNAPSHOT + 0.22.0 org.commonmark commonmark-ext-task-list-items - 0.22.0-SNAPSHOT + 0.22.0 org.commonmark commonmark-ext-yaml-front-matter - 0.22.0-SNAPSHOT + 0.22.0 org.commonmark commonmark-test-util - 0.22.0-SNAPSHOT + 0.22.0 @@ -281,7 +281,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - HEAD + commonmark-parent-0.22.0 From 5ba9c7859908e1d8d616f89285b54b04a55c47c7 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Fri, 15 Mar 2024 11:47:04 +0000 Subject: [PATCH 078/345] [maven-release-plugin] prepare for next development iteration --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 24 ++++++++++++------------ 12 files changed, 23 insertions(+), 23 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 0d9f6098e..8bd242049 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0 + 0.22.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 0cd3dd435..b119b57f8 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0 + 0.22.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 0a0873667..5c031287e 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0 + 0.22.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 0238d40cb..a7b16ef1c 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0 + 0.22.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 21c8c43c8..ed654760f 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0 + 0.22.1-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 3c49491d9..2e00c6c16 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0 + 0.22.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 15e2bc0a5..68b56e950 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0 + 0.22.1-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index d0acc4176..7ecf7336d 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.22.0 + 0.22.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 9558a65b8..bbd4c8a74 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0 + 0.22.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 58a8c94ac..dd115fdad 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0 + 0.22.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index c6501f806..bcb5b3bf0 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.0 + 0.22.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 693760923..d4e49cd80 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.22.0 + 0.22.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -111,52 +111,52 @@ org.commonmark commonmark - 0.22.0 + 0.22.1-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.22.0 + 0.22.1-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.22.0 + 0.22.1-SNAPSHOT org.commonmark commonmark-ext-ins - 0.22.0 + 0.22.1-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.22.0 + 0.22.1-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.22.0 + 0.22.1-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.22.0 + 0.22.1-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.22.0 + 0.22.1-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.22.0 + 0.22.1-SNAPSHOT org.commonmark commonmark-test-util - 0.22.0 + 0.22.1-SNAPSHOT @@ -281,7 +281,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - commonmark-parent-0.22.0 + HEAD From b570e1bda7732c37b748155fb5dc2716c0517a20 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 6 Apr 2024 12:06:32 +0700 Subject: [PATCH 079/345] Fix link reference definition parsing with invalid title An input like this: ``` [foo]: /url "title" bad ``` Means the second line is just a paragraph because only spaces/tabs are allowed after a title. The parser used to set the title to "title" in this case and assign the source span of the second line to the definition, which is wrong. Fixes #315. --- CHANGELOG.md | 7 ++++++ .../LinkReferenceDefinitionParser.java | 10 ++++++-- .../LinkReferenceDefinitionParserTest.java | 24 +++++++++++++++++++ .../org/commonmark/test/SourceSpansTest.java | 9 +++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e31b8d15a..df6beb202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## Unreleased +### Fixed +- Fix parsing of link reference definitions where it looks like it has a title + but it doesn't because it's followed by characters other than space/tab. In that + case, the title was set to the partially-parsed title and the source spans were + wrong (#315). + ## [0.22.0] - 2024-03-15 ### Added - New `MarkdownRenderer` for rendering nodes to Markdown (CommonMark)! diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index ebe6ebb23..b58e669ef 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -15,7 +15,7 @@ /** * Parser for link reference definitions at the beginning of a paragraph. * - * @see Link reference definitions + * @see Link reference definitions */ public class LinkReferenceDefinitionParser { @@ -70,6 +70,9 @@ public void parse(SourceLine line) { // Parsing failed, which means we fall back to treating text as a paragraph. if (!success) { state = State.PARAGRAPH; + // If parsing of the title part failed, we still have a valid reference that we can add, and we need to + // do it before the source span for this line is added. + finishReference(); return; } } @@ -218,7 +221,8 @@ private boolean startTitle(Scanner scanner) { private boolean title(Scanner scanner) { Position start = scanner.position(); if (!LinkScanner.scanLinkTitleContent(scanner, titleDelimiter)) { - // Invalid title, stop + // Invalid title, stop. Title collected so far must not be used. + title = null; return false; } @@ -235,6 +239,8 @@ private boolean title(Scanner scanner) { scanner.whitespace(); if (scanner.hasNext()) { // spec: No further non-whitespace characters may occur on the line. + // Title collected so far must not be used. + title = null; return false; } referenceValid = true; diff --git a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java index b4f57739b..3f22adac6 100644 --- a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java @@ -145,6 +145,30 @@ public void testTitleMultiline2() { assertDef(parser.getDefinitions().get(0), "foo", "/url", "\ntitle"); } + @Test + public void testTitleMultiline3() { + parse("[foo]: /url"); + assertEquals(State.START_TITLE, parser.getState()); + // Note that this looks like a valid title until we parse "bad", at which point we need to treat the whole line + // as a paragraph line and discard any already parsed title. + parse("\"title\" bad"); + assertEquals(State.PARAGRAPH, parser.getState()); + + assertDef(parser.getDefinitions().get(0), "foo", "/url", null); + } + + @Test + public void testTitleMultiline4() { + parse("[foo]: /url"); + assertEquals(State.START_TITLE, parser.getState()); + parse("(title"); + assertEquals(State.TITLE, parser.getState()); + parse("foo("); + assertEquals(State.PARAGRAPH, parser.getState()); + + assertDef(parser.getDefinitions().get(0), "foo", "/url", null); + } + @Test public void testTitleInvalid() { assertParagraph("[foo]: /url (invalid("); diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index 959598935..59241e49d 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -183,6 +183,15 @@ public void linkReferenceDefinitionWithTitle() { assertEquals(List.of(SourceSpan.of(1, 0, 11)), def2.getSourceSpans()); } + @Test + public void linkReferenceDefinitionWithTitleInvalid() { + var doc = PARSER.parse("[foo]: /url\n\"title\" ok\n"); + var def = Nodes.find(doc, LinkReferenceDefinition.class); + var paragraph = Nodes.find(doc, Paragraph.class); + assertEquals(List.of(SourceSpan.of(0, 0, 11)), def.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(1, 0, 10)), paragraph.getSourceSpans()); + } + @Test public void linkReferenceDefinitionHeading() { // This is probably the trickiest because we have a link reference definition at the start of a paragraph From d8fce115d75114586f233ae9e74c9ac20753eeb1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 16 Apr 2024 00:02:37 +1000 Subject: [PATCH 080/345] Add getTriggerCharacter to InlineContentParser, calculate inline parsers --- .../commonmark/internal/InlineParserImpl.java | 63 ++++++++++--------- .../internal/inline/AutolinkInlineParser.java | 5 ++ .../inline/BackslashInlineParser.java | 5 ++ .../inline/BackticksInlineParser.java | 5 ++ .../internal/inline/EntityInlineParser.java | 5 ++ .../internal/inline/HtmlInlineParser.java | 5 ++ .../internal/inline/InlineContentParser.java | 13 ++++ .../internal/inline/InlineParserState.java | 4 +- 8 files changed, 73 insertions(+), 32 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 113e80db9..e5a07091b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -16,10 +16,10 @@ public class InlineParserImpl implements InlineParser, InlineParserState { - private final BitSet specialCharacters; - private final Map delimiterProcessors; private final InlineParserContext context; private final Map> inlineParsers; + private final Map delimiterProcessors; + private final BitSet specialCharacters; private Scanner scanner; private boolean includeSourceSpans; @@ -37,45 +37,28 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private Bracket lastBracket; public InlineParserImpl(InlineParserContext inlineParserContext) { - this.delimiterProcessors = calculateDelimiterProcessors(inlineParserContext.getCustomDelimiterProcessors()); - this.context = inlineParserContext; - this.inlineParsers = new HashMap<>(); - this.inlineParsers.put('\\', Collections.singletonList(new BackslashInlineParser())); - this.inlineParsers.put('`', Collections.singletonList(new BackticksInlineParser())); - this.inlineParsers.put('&', Collections.singletonList(new EntityInlineParser())); - this.inlineParsers.put('<', Arrays.asList(new AutolinkInlineParser(), new HtmlInlineParser())); - + this.inlineParsers = calculateInlineContentParsers(); + this.delimiterProcessors = calculateDelimiterProcessors(inlineParserContext.getCustomDelimiterProcessors()); this.specialCharacters = calculateSpecialCharacters(this.delimiterProcessors.keySet(), inlineParsers.keySet()); } - public static BitSet calculateSpecialCharacters(Set delimiterCharacters, Set characters) { - BitSet bitSet = new BitSet(); - for (Character c : delimiterCharacters) { - bitSet.set(c); + private static Map> calculateInlineContentParsers() { + var map = new HashMap>(); + for (var parser : List.of(new BackslashInlineParser(), new BackticksInlineParser(), new EntityInlineParser(), + new AutolinkInlineParser(), new HtmlInlineParser())) { + map.computeIfAbsent(parser.getTriggerCharacter(), k -> new ArrayList<>()).add(parser); } - for (Character c : characters) { - bitSet.set(c); - } - bitSet.set('['); - bitSet.set(']'); - bitSet.set('!'); - bitSet.set('\n'); - return bitSet; + return map; } - public static Map calculateDelimiterProcessors(List delimiterProcessors) { - Map map = new HashMap<>(); - addDelimiterProcessors(Arrays.asList(new AsteriskDelimiterProcessor(), new UnderscoreDelimiterProcessor()), map); + private static Map calculateDelimiterProcessors(List delimiterProcessors) { + var map = new HashMap(); + addDelimiterProcessors(List.of(new AsteriskDelimiterProcessor(), new UnderscoreDelimiterProcessor()), map); addDelimiterProcessors(delimiterProcessors, map); return map; } - @Override - public Scanner scanner() { - return scanner; - } - private static void addDelimiterProcessors(Iterable delimiterProcessors, Map map) { for (DelimiterProcessor delimiterProcessor : delimiterProcessors) { char opening = delimiterProcessor.getOpeningCharacter(); @@ -109,6 +92,26 @@ private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterPr } } + private static BitSet calculateSpecialCharacters(Set delimiterCharacters, Set characters) { + BitSet bitSet = new BitSet(); + for (Character c : delimiterCharacters) { + bitSet.set(c); + } + for (Character c : characters) { + bitSet.set(c); + } + bitSet.set('['); + bitSet.set(']'); + bitSet.set('!'); + bitSet.set('\n'); + return bitSet; + } + + @Override + public Scanner scanner() { + return scanner; + } + /** * Parse content in block into inline children, appending them to the block node. */ diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java index 36c43e196..1d27f43c9 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -19,6 +19,11 @@ public class AutolinkInlineParser implements InlineContentParser { private static final Pattern EMAIL = Pattern .compile("^([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$"); + @Override + public char getTriggerCharacter() { + return '<'; + } + @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java index 02c136951..768875174 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java @@ -15,6 +15,11 @@ public class BackslashInlineParser implements InlineContentParser { private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE); + @Override + public char getTriggerCharacter() { + return '\\'; + } + @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java index bef8e1f99..1c12b2fd4 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -12,6 +12,11 @@ */ public class BackticksInlineParser implements InlineContentParser { + @Override + public char getTriggerCharacter() { + return '`'; + } + @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java index 2b7d296fb..4dfd94e9f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java @@ -16,6 +16,11 @@ public class EntityInlineParser implements InlineContentParser { private static final AsciiMatcher entityStart = AsciiMatcher.builder().range('A', 'Z').range('a', 'z').build(); private static final AsciiMatcher entityContinue = entityStart.newBuilder().range('0', '9').build(); + @Override + public char getTriggerCharacter() { + return '&'; + } + @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java index 6dc525cb9..f776691df 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -26,6 +26,11 @@ public class HtmlInlineParser implements InlineContentParser { .c('"').c('\'').c('=').c('<').c('>').c('`') .build(); + @Override + public char getTriggerCharacter() { + return '<'; + } + @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java index 755ee3135..2dcaf1653 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java @@ -2,5 +2,18 @@ public interface InlineContentParser { + /** + * An inline content parser needs to have a special "trigger" character which activates it. If this character is + * encountered during inline parsing, {@link #tryParse} is called with the current parser state. + */ + char getTriggerCharacter(); + + /** + * Try to parse the inline content. Note that the character at the current position is the + * {@link #getTriggerCharacter()}. + * + * @param inlineParserState the current state of the inline parser + * @return the result of parsing; can indicate that this parser is not interested, or that parsing was successful + */ ParsedInline tryParse(InlineParserState inlineParserState); } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java b/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java index ea8689be5..ba7369617 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java @@ -6,8 +6,8 @@ public interface InlineParserState { /** - * Return a scanner for the input for the current position (on the character that the inline parser registered - * interest for). + * Return a scanner for the input for the current position (on the trigger character that the inline parser was + * added for). *

      * Note that this always returns the same instance, if you want to backtrack you need to use * {@link Scanner#position()} and {@link Scanner#setPosition(Position)}. From 1c0259d93d14ab3a7f12a45db78d19d04c5e011a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 16 Apr 2024 00:14:46 +1000 Subject: [PATCH 081/345] Cleanups --- .../commonmark/internal/InlineParserImpl.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index e5a07091b..364484a7c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -120,14 +120,13 @@ public void parse(SourceLines lines, Node block) { reset(lines); while (true) { - List nodes = parseInline(); - if (nodes != null) { - for (Node node : nodes) { - block.appendChild(node); - } - } else { + var nodes = parseInline(); + if (nodes == null) { break; } + for (Node node : nodes) { + block.appendChild(node); + } } processDelimiters(null); @@ -158,20 +157,20 @@ private List parseInline() { switch (c) { case '[': - return Collections.singletonList(parseOpenBracket()); + return List.of(parseOpenBracket()); case '!': - return Collections.singletonList(parseBang()); + return List.of(parseBang()); case ']': - return Collections.singletonList(parseCloseBracket()); + return List.of(parseCloseBracket()); case '\n': - return Collections.singletonList(parseLineBreak()); + return List.of(parseLineBreak()); case Scanner.END: return null; } // No inline parser, delimiter or other special handling. if (!specialCharacters.get(c)) { - return Collections.singletonList(parseText()); + return List.of(parseText()); } List inlineParsers = this.inlineParsers.get(c); @@ -186,7 +185,7 @@ private List parseInline() { if (includeSourceSpans && node.getSourceSpans().isEmpty()) { node.setSourceSpans(scanner.getSource(position, scanner.position()).getSourceSpans()); } - return Collections.singletonList(node); + return List.of(node); } else { // Reset position scanner.setPosition(position); @@ -203,7 +202,7 @@ private List parseInline() { } // If we get here, even for a special/delimiter character, we will just treat it as text. - return Collections.singletonList(parseText()); + return List.of(parseText()); } /** From 0dc0c2ececf045241dd5b79f28527124df4aef47 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 16 Apr 2024 22:23:58 +1000 Subject: [PATCH 082/345] Add customInlineContentParser and use in inline parsing --- .../commonmark/internal/DocumentParser.java | 8 ++- .../internal/InlineParserContextImpl.java | 12 +++- .../commonmark/internal/InlineParserImpl.java | 14 +++-- .../parser/InlineParserContext.java | 6 ++ .../java/org/commonmark/parser/Parser.java | 41 +++++++++----- .../parser/CustomInlineContentParserTest.java | 55 +++++++++++++++++++ .../test/InlineParserContextTest.java | 6 ++ 7 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/parser/CustomInlineContentParserTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 2cc37e306..89bedf8cb 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -1,5 +1,6 @@ package org.commonmark.internal; +import org.commonmark.internal.inline.InlineContentParser; import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.*; @@ -66,6 +67,7 @@ public class DocumentParser implements ParserState { private final List blockParserFactories; private final InlineParserFactory inlineParserFactory; + private final List inlineContentParsers; private final List delimiterProcessors; private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; @@ -75,9 +77,11 @@ public class DocumentParser implements ParserState { private final List allBlockParsers = new ArrayList<>(); public DocumentParser(List blockParserFactories, InlineParserFactory inlineParserFactory, - List delimiterProcessors, IncludeSourceSpans includeSourceSpans) { + List inlineContentParsers, List delimiterProcessors, + IncludeSourceSpans includeSourceSpans) { this.blockParserFactories = blockParserFactories; this.inlineParserFactory = inlineParserFactory; + this.inlineContentParsers = inlineContentParsers; this.delimiterProcessors = delimiterProcessors; this.includeSourceSpans = includeSourceSpans; @@ -477,7 +481,7 @@ private void addDefinitionsFrom(ParagraphParser paragraphParser) { * Walk through a block & children recursively, parsing string content into inline content where appropriate. */ private void processInlines() { - InlineParserContextImpl context = new InlineParserContextImpl(delimiterProcessors, definitions); + InlineParserContextImpl context = new InlineParserContextImpl(inlineContentParsers, delimiterProcessors, definitions); InlineParser inlineParser = inlineParserFactory.create(context); for (BlockParser blockParser : allBlockParsers) { diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java index f485614d5..7354d9b88 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java @@ -1,23 +1,31 @@ package org.commonmark.internal; +import org.commonmark.internal.inline.InlineContentParser; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.delimiter.DelimiterProcessor; import java.util.List; -import java.util.Map; public class InlineParserContextImpl implements InlineParserContext { + private final List inlineContentParsers; private final List delimiterProcessors; private final LinkReferenceDefinitions linkReferenceDefinitions; - public InlineParserContextImpl(List delimiterProcessors, + public InlineParserContextImpl(List inlineContentParsers, + List delimiterProcessors, LinkReferenceDefinitions linkReferenceDefinitions) { + this.inlineContentParsers = inlineContentParsers; this.delimiterProcessors = delimiterProcessors; this.linkReferenceDefinitions = linkReferenceDefinitions; } + @Override + public List getCustomInlineContentParsers() { + return inlineContentParsers; + } + @Override public List getCustomDelimiterProcessors() { return delimiterProcessors; diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 364484a7c..82caed36c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -36,15 +36,19 @@ public class InlineParserImpl implements InlineParser, InlineParserState { */ private Bracket lastBracket; - public InlineParserImpl(InlineParserContext inlineParserContext) { - this.context = inlineParserContext; - this.inlineParsers = calculateInlineContentParsers(); - this.delimiterProcessors = calculateDelimiterProcessors(inlineParserContext.getCustomDelimiterProcessors()); + public InlineParserImpl(InlineParserContext context) { + this.context = context; + this.inlineParsers = calculateInlineContentParsers(context.getCustomInlineContentParsers()); + this.delimiterProcessors = calculateDelimiterProcessors(context.getCustomDelimiterProcessors()); this.specialCharacters = calculateSpecialCharacters(this.delimiterProcessors.keySet(), inlineParsers.keySet()); } - private static Map> calculateInlineContentParsers() { + private static Map> calculateInlineContentParsers(List inlineContentParsers) { var map = new HashMap>(); + // Custom parsers can override built-in parsers if they want, so make sure they are tried first + for (var parser : inlineContentParsers) { + map.computeIfAbsent(parser.getTriggerCharacter(), k -> new ArrayList<>()).add(parser); + } for (var parser : List.of(new BackslashInlineParser(), new BackticksInlineParser(), new EntityInlineParser(), new AutolinkInlineParser(), new HtmlInlineParser())) { map.computeIfAbsent(parser.getTriggerCharacter(), k -> new ArrayList<>()).add(parser); diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java index dae96e2c8..7c41f8c9a 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -1,5 +1,6 @@ package org.commonmark.parser; +import org.commonmark.internal.inline.InlineContentParser; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -10,6 +11,11 @@ */ public interface InlineParserContext { + /** + * @return custom inline content parsers that have been configured with {@link Parser.Builder#customInlineContentParser(InlineContentParser)} + */ + List getCustomInlineContentParsers(); + /** * @return custom delimiter processors that have been configured with {@link Parser.Builder#customDelimiterProcessor(DelimiterProcessor)} */ diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 89cdd584c..cb38c5b0b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -5,6 +5,7 @@ import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; import org.commonmark.internal.LinkReferenceDefinitions; +import org.commonmark.internal.inline.InlineContentParser; import org.commonmark.node.*; import org.commonmark.parser.block.BlockParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -13,6 +14,7 @@ import java.io.Reader; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Set; @@ -28,6 +30,7 @@ public class Parser { private final List blockParserFactories; + private final List inlineContentParsers; private final List delimiterProcessors; private final InlineParserFactory inlineParserFactory; private final List postProcessors; @@ -37,12 +40,13 @@ private Parser(Builder builder) { this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes); this.inlineParserFactory = builder.getInlineParserFactory(); this.postProcessors = builder.postProcessors; + this.inlineContentParsers = builder.inlineContentParsers; this.delimiterProcessors = builder.delimiterProcessors; this.includeSourceSpans = builder.includeSourceSpans; // Try to construct an inline parser. Invalid configuration might result in an exception, which we want to // detect as soon as possible. - this.inlineParserFactory.create(new InlineParserContextImpl(delimiterProcessors, new LinkReferenceDefinitions())); + this.inlineParserFactory.create(new InlineParserContextImpl(inlineContentParsers, delimiterProcessors, new LinkReferenceDefinitions())); } /** @@ -100,7 +104,7 @@ public Node parseReader(Reader input) throws IOException { } private DocumentParser createDocumentParser() { - return new DocumentParser(blockParserFactories, inlineParserFactory, delimiterProcessors, includeSourceSpans); + return new DocumentParser(blockParserFactories, inlineParserFactory, inlineContentParsers, delimiterProcessors, includeSourceSpans); } private Node postProcess(Node document) { @@ -115,6 +119,7 @@ private Node postProcess(Node document) { */ public static class Builder { private final List blockParserFactories = new ArrayList<>(); + private final List inlineContentParsers = new ArrayList<>(); private final List delimiterProcessors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); @@ -169,7 +174,7 @@ public Builder extensions(Iterable extensions) { * * * @param enabledBlockTypes A list of block nodes the parser will parse. - * If this list is empty, the parser will not recognize any CommonMark core features. + * If this list is empty, the parser will not recognize any CommonMark core features. * @return {@code this} */ public Builder enabledBlockTypes(Set> enabledBlockTypes) { @@ -196,7 +201,7 @@ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { } /** - * Adds a custom block parser factory. + * Add a custom block parser factory. *

      * Note that custom factories are applied before the built-in factories. This is so that * extensions can change how some syntax is parsed that would otherwise be handled by built-in factories. @@ -214,7 +219,23 @@ public Builder customBlockParserFactory(BlockParserFactory blockParserFactory) { } /** - * Adds a custom delimiter processor. + * Add a custom inline content parser, for additional inline parsing or overriding built-in parsing. + *

      + * Note that parsers are triggered based on a special character as specified by + * {@link InlineContentParser#getTriggerCharacter()}. It is possible to register multiple parsers for the same + * character, or even for some built-in special character such as {@code `}. + * + * @param inlineContentParser + * @return + */ + public Builder customInlineContentParser(InlineContentParser inlineContentParser) { + Objects.requireNonNull(inlineContentParser, "inlineContentParser must not be null"); + inlineContentParsers.add(inlineContentParser); + return this; + } + + /** + * Add a custom delimiter processor. *

      * Note that multiple delimiter processors with the same characters can be added, as long as they have a * different minimum length. In that case, the processor with the shortest matching length is used. Adding more @@ -263,15 +284,7 @@ public Builder inlineParserFactory(InlineParserFactory inlineParserFactory) { } private InlineParserFactory getInlineParserFactory() { - if (inlineParserFactory != null) { - return inlineParserFactory; - } - return new InlineParserFactory() { - @Override - public InlineParser create(InlineParserContext inlineParserContext) { - return new InlineParserImpl(inlineParserContext); - } - }; + return Objects.requireNonNullElseGet(inlineParserFactory, () -> InlineParserImpl::new); } } diff --git a/commonmark/src/test/java/org/commonmark/parser/CustomInlineContentParserTest.java b/commonmark/src/test/java/org/commonmark/parser/CustomInlineContentParserTest.java new file mode 100644 index 000000000..a88053912 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/parser/CustomInlineContentParserTest.java @@ -0,0 +1,55 @@ +package org.commonmark.parser; + +import org.commonmark.internal.inline.InlineContentParser; +import org.commonmark.internal.inline.InlineParserState; +import org.commonmark.internal.inline.ParsedInline; +import org.commonmark.node.CustomNode; +import org.commonmark.test.Nodes; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class CustomInlineContentParserTest { + + @Test + public void customInlineContentParser() { + var parser = Parser.builder().customInlineContentParser(new DollarInlineContentParser()).build(); + var doc = parser.parse("Test: $hey *there*$"); + var dollarInline = Nodes.find(doc, DollarInline.class); + assertEquals("hey *there*", dollarInline.getLiteral()); + } + + private static class DollarInline extends CustomNode { + private final String literal; + + public DollarInline(String literal) { + this.literal = literal; + } + + public String getLiteral() { + return literal; + } + } + + private static class DollarInlineContentParser implements InlineContentParser { + @Override + public char getTriggerCharacter() { + return '$'; + } + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState) { + var scanner = inlineParserState.scanner(); + scanner.next(); + var pos = scanner.position(); + + var end = scanner.find('$'); + if (end == -1) { + return ParsedInline.none(); + } + var content = scanner.getSource(pos, scanner.position()).getContent(); + scanner.next(); + return ParsedInline.of(new DollarInline(content), scanner.position()); + } + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java index b7d083df3..7fd875703 100644 --- a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java +++ b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java @@ -1,6 +1,7 @@ package org.commonmark.test; import org.commonmark.internal.InlineParserImpl; +import org.commonmark.internal.inline.InlineContentParser; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; @@ -41,6 +42,11 @@ static class CapturingInlineParserFactory implements InlineParserFactory { @Override public InlineParser create(final InlineParserContext inlineParserContext) { InlineParserContext wrappedContext = new InlineParserContext() { + @Override + public List getCustomInlineContentParsers() { + return inlineParserContext.getCustomInlineContentParsers(); + } + @Override public List getCustomDelimiterProcessors() { return inlineParserContext.getCustomDelimiterProcessors(); From 07735411f6654700c46c1b6f6d46176f0fe66dc0 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 22 Apr 2024 09:36:38 +1000 Subject: [PATCH 083/345] Add factory so that inline parsers can keep state --- .../commonmark/internal/DocumentParser.java | 10 +-- .../internal/InlineParserContextImpl.java | 12 +-- .../commonmark/internal/InlineParserImpl.java | 41 +++++---- .../internal/inline/AutolinkInlineParser.java | 17 ++-- .../inline/BackslashInlineParser.java | 17 ++-- .../inline/BackticksInlineParser.java | 17 ++-- .../internal/inline/EntityInlineParser.java | 22 +++-- .../internal/inline/HtmlInlineParser.java | 20 +++-- .../internal/inline/InlineContentParser.java | 18 ++-- .../inline/InlineContentParserFactory.java | 16 ++++ .../parser/InlineParserContext.java | 6 +- .../parser/InlineParserFactory.java | 4 + .../java/org/commonmark/parser/Parser.java | 22 ++--- .../parser/CustomInlineContentParserTest.java | 55 ------------ .../parser/InlineContentParserTest.java | 85 +++++++++++++++++++ .../test/InlineParserContextTest.java | 6 +- 16 files changed, 233 insertions(+), 135 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParserFactory.java delete mode 100644 commonmark/src/test/java/org/commonmark/parser/CustomInlineContentParserTest.java create mode 100644 commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 89bedf8cb..afb6ed9dd 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -1,6 +1,6 @@ package org.commonmark.internal; -import org.commonmark.internal.inline.InlineContentParser; +import org.commonmark.internal.inline.InlineContentParserFactory; import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.*; @@ -67,7 +67,7 @@ public class DocumentParser implements ParserState { private final List blockParserFactories; private final InlineParserFactory inlineParserFactory; - private final List inlineContentParsers; + private final List inlineContentParserFactories; private final List delimiterProcessors; private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; @@ -77,11 +77,11 @@ public class DocumentParser implements ParserState { private final List allBlockParsers = new ArrayList<>(); public DocumentParser(List blockParserFactories, InlineParserFactory inlineParserFactory, - List inlineContentParsers, List delimiterProcessors, + List inlineContentParserFactories, List delimiterProcessors, IncludeSourceSpans includeSourceSpans) { this.blockParserFactories = blockParserFactories; this.inlineParserFactory = inlineParserFactory; - this.inlineContentParsers = inlineContentParsers; + this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; this.includeSourceSpans = includeSourceSpans; @@ -481,7 +481,7 @@ private void addDefinitionsFrom(ParagraphParser paragraphParser) { * Walk through a block & children recursively, parsing string content into inline content where appropriate. */ private void processInlines() { - InlineParserContextImpl context = new InlineParserContextImpl(inlineContentParsers, delimiterProcessors, definitions); + InlineParserContextImpl context = new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, definitions); InlineParser inlineParser = inlineParserFactory.create(context); for (BlockParser blockParser : allBlockParsers) { diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java index 7354d9b88..c8d927246 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java @@ -1,6 +1,6 @@ package org.commonmark.internal; -import org.commonmark.internal.inline.InlineContentParser; +import org.commonmark.internal.inline.InlineContentParserFactory; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -9,21 +9,21 @@ public class InlineParserContextImpl implements InlineParserContext { - private final List inlineContentParsers; + private final List inlineContentParserFactories; private final List delimiterProcessors; private final LinkReferenceDefinitions linkReferenceDefinitions; - public InlineParserContextImpl(List inlineContentParsers, + public InlineParserContextImpl(List inlineContentParserFactories, List delimiterProcessors, LinkReferenceDefinitions linkReferenceDefinitions) { - this.inlineContentParsers = inlineContentParsers; + this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; this.linkReferenceDefinitions = linkReferenceDefinitions; } @Override - public List getCustomInlineContentParsers() { - return inlineContentParsers; + public List getCustomInlineContentParserFactories() { + return inlineContentParserFactories; } @Override diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 82caed36c..3d26993db 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -17,10 +17,11 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private final InlineParserContext context; - private final Map> inlineParsers; + private final List inlineContentParserFactories; private final Map delimiterProcessors; private final BitSet specialCharacters; + private Map> inlineParsers; private Scanner scanner; private boolean includeSourceSpans; private int trailingSpaces; @@ -38,22 +39,20 @@ public class InlineParserImpl implements InlineParser, InlineParserState { public InlineParserImpl(InlineParserContext context) { this.context = context; - this.inlineParsers = calculateInlineContentParsers(context.getCustomInlineContentParsers()); + this.inlineContentParserFactories = calculateInlineContentParserFactories(context.getCustomInlineContentParserFactories()); this.delimiterProcessors = calculateDelimiterProcessors(context.getCustomDelimiterProcessors()); - this.specialCharacters = calculateSpecialCharacters(this.delimiterProcessors.keySet(), inlineParsers.keySet()); + this.specialCharacters = calculateSpecialCharacters(this.delimiterProcessors.keySet(), this.inlineContentParserFactories); } - private static Map> calculateInlineContentParsers(List inlineContentParsers) { - var map = new HashMap>(); + private List calculateInlineContentParserFactories(List customFactories) { // Custom parsers can override built-in parsers if they want, so make sure they are tried first - for (var parser : inlineContentParsers) { - map.computeIfAbsent(parser.getTriggerCharacter(), k -> new ArrayList<>()).add(parser); - } - for (var parser : List.of(new BackslashInlineParser(), new BackticksInlineParser(), new EntityInlineParser(), - new AutolinkInlineParser(), new HtmlInlineParser())) { - map.computeIfAbsent(parser.getTriggerCharacter(), k -> new ArrayList<>()).add(parser); - } - return map; + var list = new ArrayList<>(customFactories); + list.add(new BackslashInlineParser.Factory()); + list.add(new BackticksInlineParser.Factory()); + list.add(new EntityInlineParser.Factory()); + list.add(new AutolinkInlineParser.Factory()); + list.add(new HtmlInlineParser.Factory()); + return list; } private static Map calculateDelimiterProcessors(List delimiterProcessors) { @@ -96,13 +95,14 @@ private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterPr } } - private static BitSet calculateSpecialCharacters(Set delimiterCharacters, Set characters) { + private static BitSet calculateSpecialCharacters(Set delimiterCharacters, + List inlineContentParserFactories) { BitSet bitSet = new BitSet(); for (Character c : delimiterCharacters) { bitSet.set(c); } - for (Character c : characters) { - bitSet.set(c); + for (var factory : inlineContentParserFactories) { + bitSet.set(factory.getTriggerCharacter()); } bitSet.set('['); bitSet.set(']'); @@ -111,6 +111,14 @@ private static BitSet calculateSpecialCharacters(Set delimiterCharact return bitSet; } + private Map> createInlineContentParsers() { + var map = new HashMap>(); + for (var factory : inlineContentParserFactories) { + map.computeIfAbsent(factory.getTriggerCharacter(), k -> new ArrayList<>()).add(factory.create()); + } + return map; + } + @Override public Scanner scanner() { return scanner; @@ -143,6 +151,7 @@ void reset(SourceLines lines) { this.trailingSpaces = 0; this.lastDelimiter = null; this.lastBracket = null; + this.inlineParsers = createInlineContentParsers(); } private Text text(SourceLines sourceLines) { diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java index 1d27f43c9..55a0e46a1 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -19,11 +19,6 @@ public class AutolinkInlineParser implements InlineContentParser { private static final Pattern EMAIL = Pattern .compile("^([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$"); - @Override - public char getTriggerCharacter() { - return '<'; - } - @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); @@ -51,4 +46,16 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { } return ParsedInline.none(); } + + public static class Factory implements InlineContentParserFactory { + @Override + public char getTriggerCharacter() { + return '<'; + } + + @Override + public InlineContentParser create() { + return new AutolinkInlineParser(); + } + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java index 768875174..f2133baaa 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java @@ -15,11 +15,6 @@ public class BackslashInlineParser implements InlineContentParser { private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE); - @Override - public char getTriggerCharacter() { - return '\\'; - } - @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); @@ -37,4 +32,16 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { return ParsedInline.of(new Text("\\"), scanner.position()); } } + + public static class Factory implements InlineContentParserFactory { + @Override + public char getTriggerCharacter() { + return '\\'; + } + + @Override + public InlineContentParser create() { + return new BackslashInlineParser(); + } + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java index 1c12b2fd4..80286f578 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -12,11 +12,6 @@ */ public class BackticksInlineParser implements InlineContentParser { - @Override - public char getTriggerCharacter() { - return '`'; - } - @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); @@ -52,4 +47,16 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { Text text = new Text(source.getContent()); return ParsedInline.of(text, afterOpening); } + + public static class Factory implements InlineContentParserFactory { + @Override + public char getTriggerCharacter() { + return '`'; + } + + @Override + public InlineContentParser create() { + return new BackticksInlineParser(); + } + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java index 4dfd94e9f..8e45b26ce 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java @@ -1,13 +1,13 @@ package org.commonmark.internal.inline; -import org.commonmark.text.AsciiMatcher; import org.commonmark.internal.util.Html5Entities; import org.commonmark.node.Text; import org.commonmark.parser.beta.Position; import org.commonmark.parser.beta.Scanner; +import org.commonmark.text.AsciiMatcher; /** - * Attempts to parse a HTML entity or numeric character reference. + * Attempts to parse an HTML entity or numeric character reference. */ public class EntityInlineParser implements InlineContentParser { @@ -16,11 +16,6 @@ public class EntityInlineParser implements InlineContentParser { private static final AsciiMatcher entityStart = AsciiMatcher.builder().range('A', 'Z').range('a', 'z').build(); private static final AsciiMatcher entityContinue = entityStart.newBuilder().range('0', '9').build(); - @Override - public char getTriggerCharacter() { - return '&'; - } - @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); @@ -57,4 +52,17 @@ private ParsedInline entity(Scanner scanner, Position start) { String text = scanner.getSource(start, scanner.position()).getContent(); return ParsedInline.of(new Text(Html5Entities.entityToString(text)), scanner.position()); } + + public static class Factory implements InlineContentParserFactory { + + @Override + public char getTriggerCharacter() { + return '&'; + } + + @Override + public InlineContentParser create() { + return new EntityInlineParser(); + } + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java index f776691df..691946483 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -1,9 +1,9 @@ package org.commonmark.internal.inline; -import org.commonmark.text.AsciiMatcher; import org.commonmark.node.HtmlInline; import org.commonmark.parser.beta.Position; import org.commonmark.parser.beta.Scanner; +import org.commonmark.text.AsciiMatcher; /** * Attempt to parse inline HTML. @@ -26,11 +26,6 @@ public class HtmlInlineParser implements InlineContentParser { .c('"').c('\'').c('=').c('<').c('>').c('`') .build(); - @Override - public char getTriggerCharacter() { - return '<'; - } - @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); @@ -205,4 +200,17 @@ private static boolean tryDeclaration(Scanner scanner) { } return false; } + + public static class Factory implements InlineContentParserFactory { + + @Override + public char getTriggerCharacter() { + return '<'; + } + + @Override + public InlineContentParser create() { + return new HtmlInlineParser(); + } + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java index 2dcaf1653..e0ab413f3 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java @@ -1,16 +1,18 @@ package org.commonmark.internal.inline; +/** + * Parser for a type of inline content. Registered via a {@link InlineContentParserFactory} and created by its + * {@link InlineContentParserFactory#create() create} method. The lifetime of this is tied to each inline content + * snippet that is parsed, as a new instance is created for each. + */ public interface InlineContentParser { /** - * An inline content parser needs to have a special "trigger" character which activates it. If this character is - * encountered during inline parsing, {@link #tryParse} is called with the current parser state. - */ - char getTriggerCharacter(); - - /** - * Try to parse the inline content. Note that the character at the current position is the - * {@link #getTriggerCharacter()}. + * Try to parse inline content starting from the current position. Note that the character at the current position + * is the {@link InlineContentParserFactory#getTriggerCharacter()} of the factory that created this parser. + *

      + * For a given inline content snippet that is being parsed, this method can be called multiple times: each time a + * trigger character is encountered. * * @param inlineParserState the current state of the inline parser * @return the result of parsing; can indicate that this parser is not interested, or that parsing was successful diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParserFactory.java b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParserFactory.java new file mode 100644 index 000000000..b8d83be66 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParserFactory.java @@ -0,0 +1,16 @@ +package org.commonmark.internal.inline; + +public interface InlineContentParserFactory { + + /** + * An inline content parser needs to have a special "trigger" character which activates it. When this character is + * encountered during inline parsing, {@link InlineContentParser#tryParse} is called with the current parser state. + */ + char getTriggerCharacter(); + + /** + * Create an {@link InlineContentParser} that will do the parsing. Create is called once per text snippet of inline + * content inside block structures, and then called each time a trigger character is encountered. + */ + InlineContentParser create(); +} diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java index 7c41f8c9a..1c2594033 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -1,6 +1,6 @@ package org.commonmark.parser; -import org.commonmark.internal.inline.InlineContentParser; +import org.commonmark.internal.inline.InlineContentParserFactory; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -12,9 +12,9 @@ public interface InlineParserContext { /** - * @return custom inline content parsers that have been configured with {@link Parser.Builder#customInlineContentParser(InlineContentParser)} + * @return custom inline content parsers that have been configured with {@link Parser.Builder#customInlineContentParser(InlineContentParserFactory)} */ - List getCustomInlineContentParsers(); + List getCustomInlineContentParserFactories(); /** * @return custom delimiter processors that have been configured with {@link Parser.Builder#customDelimiterProcessor(DelimiterProcessor)} diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java index 34c384a8a..c1640e9d8 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java @@ -4,5 +4,9 @@ * Factory for custom inline parser. */ public interface InlineParserFactory { + + /** + * Create an {@link InlineParser} to use for parsing inlines. This is called once per parsed document. + */ InlineParser create(InlineParserContext inlineParserContext); } diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index cb38c5b0b..75547c422 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -5,7 +5,7 @@ import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; import org.commonmark.internal.LinkReferenceDefinitions; -import org.commonmark.internal.inline.InlineContentParser; +import org.commonmark.internal.inline.InlineContentParserFactory; import org.commonmark.node.*; import org.commonmark.parser.block.BlockParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -30,7 +30,7 @@ public class Parser { private final List blockParserFactories; - private final List inlineContentParsers; + private final List inlineContentParserFactories; private final List delimiterProcessors; private final InlineParserFactory inlineParserFactory; private final List postProcessors; @@ -40,13 +40,13 @@ private Parser(Builder builder) { this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes); this.inlineParserFactory = builder.getInlineParserFactory(); this.postProcessors = builder.postProcessors; - this.inlineContentParsers = builder.inlineContentParsers; + this.inlineContentParserFactories = builder.inlineContentParserFactories; this.delimiterProcessors = builder.delimiterProcessors; this.includeSourceSpans = builder.includeSourceSpans; // Try to construct an inline parser. Invalid configuration might result in an exception, which we want to // detect as soon as possible. - this.inlineParserFactory.create(new InlineParserContextImpl(inlineContentParsers, delimiterProcessors, new LinkReferenceDefinitions())); + this.inlineParserFactory.create(new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, new LinkReferenceDefinitions())); } /** @@ -104,7 +104,7 @@ public Node parseReader(Reader input) throws IOException { } private DocumentParser createDocumentParser() { - return new DocumentParser(blockParserFactories, inlineParserFactory, inlineContentParsers, delimiterProcessors, includeSourceSpans); + return new DocumentParser(blockParserFactories, inlineParserFactory, inlineContentParserFactories, delimiterProcessors, includeSourceSpans); } private Node postProcess(Node document) { @@ -119,7 +119,7 @@ private Node postProcess(Node document) { */ public static class Builder { private final List blockParserFactories = new ArrayList<>(); - private final List inlineContentParsers = new ArrayList<>(); + private final List inlineContentParserFactories = new ArrayList<>(); private final List delimiterProcessors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); @@ -222,15 +222,15 @@ public Builder customBlockParserFactory(BlockParserFactory blockParserFactory) { * Add a custom inline content parser, for additional inline parsing or overriding built-in parsing. *

      * Note that parsers are triggered based on a special character as specified by - * {@link InlineContentParser#getTriggerCharacter()}. It is possible to register multiple parsers for the same + * {@link InlineContentParserFactory#getTriggerCharacter()}. It is possible to register multiple parsers for the same * character, or even for some built-in special character such as {@code `}. * - * @param inlineContentParser + * @param inlineContentParserFactory * @return */ - public Builder customInlineContentParser(InlineContentParser inlineContentParser) { - Objects.requireNonNull(inlineContentParser, "inlineContentParser must not be null"); - inlineContentParsers.add(inlineContentParser); + public Builder customInlineContentParser(InlineContentParserFactory inlineContentParserFactory) { + Objects.requireNonNull(inlineContentParserFactory, "inlineContentParser must not be null"); + inlineContentParserFactories.add(inlineContentParserFactory); return this; } diff --git a/commonmark/src/test/java/org/commonmark/parser/CustomInlineContentParserTest.java b/commonmark/src/test/java/org/commonmark/parser/CustomInlineContentParserTest.java deleted file mode 100644 index a88053912..000000000 --- a/commonmark/src/test/java/org/commonmark/parser/CustomInlineContentParserTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.commonmark.parser; - -import org.commonmark.internal.inline.InlineContentParser; -import org.commonmark.internal.inline.InlineParserState; -import org.commonmark.internal.inline.ParsedInline; -import org.commonmark.node.CustomNode; -import org.commonmark.test.Nodes; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class CustomInlineContentParserTest { - - @Test - public void customInlineContentParser() { - var parser = Parser.builder().customInlineContentParser(new DollarInlineContentParser()).build(); - var doc = parser.parse("Test: $hey *there*$"); - var dollarInline = Nodes.find(doc, DollarInline.class); - assertEquals("hey *there*", dollarInline.getLiteral()); - } - - private static class DollarInline extends CustomNode { - private final String literal; - - public DollarInline(String literal) { - this.literal = literal; - } - - public String getLiteral() { - return literal; - } - } - - private static class DollarInlineContentParser implements InlineContentParser { - @Override - public char getTriggerCharacter() { - return '$'; - } - - @Override - public ParsedInline tryParse(InlineParserState inlineParserState) { - var scanner = inlineParserState.scanner(); - scanner.next(); - var pos = scanner.position(); - - var end = scanner.find('$'); - if (end == -1) { - return ParsedInline.none(); - } - var content = scanner.getSource(pos, scanner.position()).getContent(); - scanner.next(); - return ParsedInline.of(new DollarInline(content), scanner.position()); - } - } -} diff --git a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java new file mode 100644 index 000000000..083914676 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java @@ -0,0 +1,85 @@ +package org.commonmark.parser; + +import org.commonmark.internal.inline.InlineContentParser; +import org.commonmark.internal.inline.InlineContentParserFactory; +import org.commonmark.internal.inline.InlineParserState; +import org.commonmark.internal.inline.ParsedInline; +import org.commonmark.node.CustomNode; +import org.commonmark.node.Heading; +import org.commonmark.test.Nodes; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class InlineContentParserTest { + + @Test + public void customInlineContentParser() { + var parser = Parser.builder().customInlineContentParser(new DollarInlineParser.Factory()).build(); + var doc = parser.parse("Test: $hey *there*$ $you$\n\n# Heading $heading$\n"); + var inline1 = Nodes.find(doc, DollarInline.class); + assertEquals("hey *there*", inline1.getLiteral()); + + var inline2 = (DollarInline) doc.getFirstChild().getLastChild(); + assertEquals("you", inline2.getLiteral()); + + var heading = Nodes.find(doc, Heading.class); + var inline3 = (DollarInline) heading.getLastChild(); + assertEquals("heading", inline3.getLiteral()); + + // Parser is created for each inline snippet, which is why the index resets for the second snippet. + assertEquals(0, inline1.getIndex()); + assertEquals(1, inline2.getIndex()); + assertEquals(0, inline3.getIndex()); + } + + private static class DollarInline extends CustomNode { + private final String literal; + private final int index; + + public DollarInline(String literal, int index) { + this.literal = literal; + this.index = index; + } + + public String getLiteral() { + return literal; + } + + public int getIndex() { + return index; + } + } + + private static class DollarInlineParser implements InlineContentParser { + + private int index = 0; + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState) { + var scanner = inlineParserState.scanner(); + scanner.next(); + var pos = scanner.position(); + + var end = scanner.find('$'); + if (end == -1) { + return ParsedInline.none(); + } + var content = scanner.getSource(pos, scanner.position()).getContent(); + scanner.next(); + return ParsedInline.of(new DollarInline(content, index++), scanner.position()); + } + + static class Factory implements InlineContentParserFactory { + @Override + public char getTriggerCharacter() { + return '$'; + } + + @Override + public InlineContentParser create() { + return new DollarInlineParser(); + } + } + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java index 7fd875703..e983870c1 100644 --- a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java +++ b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java @@ -1,7 +1,7 @@ package org.commonmark.test; import org.commonmark.internal.InlineParserImpl; -import org.commonmark.internal.inline.InlineContentParser; +import org.commonmark.internal.inline.InlineContentParserFactory; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; @@ -43,8 +43,8 @@ static class CapturingInlineParserFactory implements InlineParserFactory { public InlineParser create(final InlineParserContext inlineParserContext) { InlineParserContext wrappedContext = new InlineParserContext() { @Override - public List getCustomInlineContentParsers() { - return inlineParserContext.getCustomInlineContentParsers(); + public List getCustomInlineContentParserFactories() { + return inlineParserContext.getCustomInlineContentParserFactories(); } @Override From e7d7bcd258d7bb4cd83c2c8c7d2d6c4b81f9b0dc Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 25 Apr 2024 23:03:48 +1000 Subject: [PATCH 084/345] Allow to specify multiple trigger characters --- .../java/org/commonmark/internal/InlineParserImpl.java | 9 +++++++-- .../internal/inline/AutolinkInlineParser.java | 5 +++-- .../internal/inline/BackslashInlineParser.java | 5 +++-- .../internal/inline/BackticksInlineParser.java | 6 ++++-- .../commonmark/internal/inline/EntityInlineParser.java | 6 ++++-- .../commonmark/internal/inline/HtmlInlineParser.java | 6 ++++-- .../internal/inline/InlineContentParser.java | 2 +- .../internal/inline/InlineContentParserFactory.java | 5 ++++- .../src/main/java/org/commonmark/parser/Parser.java | 10 ++++------ .../org/commonmark/parser/InlineContentParserTest.java | 6 ++++-- 10 files changed, 38 insertions(+), 22 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 3d26993db..53020ebba 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -102,7 +102,9 @@ private static BitSet calculateSpecialCharacters(Set delimiterCharact bitSet.set(c); } for (var factory : inlineContentParserFactories) { - bitSet.set(factory.getTriggerCharacter()); + for (var c : factory.getTriggerCharacters()) { + bitSet.set(c); + } } bitSet.set('['); bitSet.set(']'); @@ -114,7 +116,10 @@ private static BitSet calculateSpecialCharacters(Set delimiterCharact private Map> createInlineContentParsers() { var map = new HashMap>(); for (var factory : inlineContentParserFactories) { - map.computeIfAbsent(factory.getTriggerCharacter(), k -> new ArrayList<>()).add(factory.create()); + var parser = factory.create(); + for (var c : factory.getTriggerCharacters()) { + map.computeIfAbsent(c, k -> new ArrayList<>()).add(parser); + } } return map; } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java index 55a0e46a1..dd898fcd1 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -6,6 +6,7 @@ import org.commonmark.parser.beta.Position; import org.commonmark.parser.beta.Scanner; +import java.util.Set; import java.util.regex.Pattern; /** @@ -49,8 +50,8 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { public static class Factory implements InlineContentParserFactory { @Override - public char getTriggerCharacter() { - return '<'; + public Set getTriggerCharacters() { + return Set.of('<'); } @Override diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java index f2133baaa..70583659f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java @@ -5,6 +5,7 @@ import org.commonmark.node.Text; import org.commonmark.parser.beta.Scanner; +import java.util.Set; import java.util.regex.Pattern; /** @@ -35,8 +36,8 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { public static class Factory implements InlineContentParserFactory { @Override - public char getTriggerCharacter() { - return '\\'; + public Set getTriggerCharacters() { + return Set.of('\\'); } @Override diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java index 80286f578..75025411c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -7,6 +7,8 @@ import org.commonmark.parser.beta.Scanner; import org.commonmark.text.Characters; +import java.util.Set; + /** * Attempt to parse backticks, returning either a backtick code span or a literal sequence of backticks. */ @@ -50,8 +52,8 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { public static class Factory implements InlineContentParserFactory { @Override - public char getTriggerCharacter() { - return '`'; + public Set getTriggerCharacters() { + return Set.of('`'); } @Override diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java index 8e45b26ce..6c226c0a5 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java @@ -6,6 +6,8 @@ import org.commonmark.parser.beta.Scanner; import org.commonmark.text.AsciiMatcher; +import java.util.Set; + /** * Attempts to parse an HTML entity or numeric character reference. */ @@ -56,8 +58,8 @@ private ParsedInline entity(Scanner scanner, Position start) { public static class Factory implements InlineContentParserFactory { @Override - public char getTriggerCharacter() { - return '&'; + public Set getTriggerCharacters() { + return Set.of('&'); } @Override diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java index 691946483..79fdd250b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -5,6 +5,8 @@ import org.commonmark.parser.beta.Scanner; import org.commonmark.text.AsciiMatcher; +import java.util.Set; + /** * Attempt to parse inline HTML. */ @@ -204,8 +206,8 @@ private static boolean tryDeclaration(Scanner scanner) { public static class Factory implements InlineContentParserFactory { @Override - public char getTriggerCharacter() { - return '<'; + public Set getTriggerCharacters() { + return Set.of('<'); } @Override diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java index e0ab413f3..cab1d467f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java @@ -9,7 +9,7 @@ public interface InlineContentParser { /** * Try to parse inline content starting from the current position. Note that the character at the current position - * is the {@link InlineContentParserFactory#getTriggerCharacter()} of the factory that created this parser. + * is one of {@link InlineContentParserFactory#getTriggerCharacters()} of the factory that created this parser. *

      * For a given inline content snippet that is being parsed, this method can be called multiple times: each time a * trigger character is encountered. diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParserFactory.java b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParserFactory.java index b8d83be66..a5e1fe592 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParserFactory.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParserFactory.java @@ -1,12 +1,15 @@ package org.commonmark.internal.inline; +import java.util.Set; + public interface InlineContentParserFactory { /** * An inline content parser needs to have a special "trigger" character which activates it. When this character is * encountered during inline parsing, {@link InlineContentParser#tryParse} is called with the current parser state. + * It can also register for more than one trigger character. */ - char getTriggerCharacter(); + Set getTriggerCharacters(); /** * Create an {@link InlineContentParser} that will do the parsing. Create is called once per text snippet of inline diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 75547c422..cedc5ac07 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -219,14 +219,12 @@ public Builder customBlockParserFactory(BlockParserFactory blockParserFactory) { } /** - * Add a custom inline content parser, for additional inline parsing or overriding built-in parsing. + * Add a factory for a custom inline content parser, for additional inline parsing or overriding built-in parsing. *

      * Note that parsers are triggered based on a special character as specified by - * {@link InlineContentParserFactory#getTriggerCharacter()}. It is possible to register multiple parsers for the same - * character, or even for some built-in special character such as {@code `}. - * - * @param inlineContentParserFactory - * @return + * {@link InlineContentParserFactory#getTriggerCharacters()}. It is possible to register multiple parsers for the same + * character, or even for some built-in special character such as {@code `}. The custom parsers are tried first + * in order in which they are registered, and then the built-in ones. */ public Builder customInlineContentParser(InlineContentParserFactory inlineContentParserFactory) { Objects.requireNonNull(inlineContentParserFactory, "inlineContentParser must not be null"); diff --git a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java index 083914676..212c9a25d 100644 --- a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java @@ -9,6 +9,8 @@ import org.commonmark.test.Nodes; import org.junit.Test; +import java.util.Set; + import static org.junit.Assert.assertEquals; public class InlineContentParserTest { @@ -72,8 +74,8 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { static class Factory implements InlineContentParserFactory { @Override - public char getTriggerCharacter() { - return '$'; + public Set getTriggerCharacters() { + return Set.of('$'); } @Override From eeb077623e47047646130005151faa9376ea8ab4 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 26 Apr 2024 17:44:19 +1000 Subject: [PATCH 085/345] Move inline content parser to beta API package --- .../commonmark/internal/DocumentParser.java | 2 +- .../internal/InlineParserContextImpl.java | 2 +- .../commonmark/internal/InlineParserImpl.java | 2 +- .../internal/inline/AutolinkInlineParser.java | 3 +-- .../inline/BackslashInlineParser.java | 2 +- .../inline/BackticksInlineParser.java | 3 +-- .../internal/inline/EntityInlineParser.java | 3 +-- .../internal/inline/HtmlInlineParser.java | 3 +-- .../internal/inline/ParsedInline.java | 24 ------------------- .../internal/inline/ParsedInlineImpl.java | 5 ++-- .../parser/InlineParserContext.java | 2 +- .../java/org/commonmark/parser/Parser.java | 2 +- .../beta}/InlineContentParser.java | 2 +- .../beta}/InlineContentParserFactory.java | 2 +- .../beta}/InlineParserState.java | 5 +--- .../commonmark/parser/beta/ParsedInline.java | 24 +++++++++++++++++++ .../parser/InlineContentParserTest.java | 8 +++---- .../test/InlineParserContextTest.java | 2 +- 18 files changed, 45 insertions(+), 51 deletions(-) delete mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java rename commonmark/src/main/java/org/commonmark/{internal/inline => parser/beta}/InlineContentParser.java (96%) rename commonmark/src/main/java/org/commonmark/{internal/inline => parser/beta}/InlineContentParserFactory.java (94%) rename commonmark/src/main/java/org/commonmark/{internal/inline => parser/beta}/InlineParserState.java (75%) create mode 100644 commonmark/src/main/java/org/commonmark/parser/beta/ParsedInline.java diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index afb6ed9dd..6884c56a9 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -1,6 +1,6 @@ package org.commonmark.internal; -import org.commonmark.internal.inline.InlineContentParserFactory; +import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.*; diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java index c8d927246..689a5372e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java @@ -1,6 +1,6 @@ package org.commonmark.internal; -import org.commonmark.internal.inline.InlineContentParserFactory; +import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.delimiter.DelimiterProcessor; diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 53020ebba..5b91a5a16 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -7,7 +7,7 @@ import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.SourceLines; -import org.commonmark.parser.beta.Position; +import org.commonmark.parser.beta.*; import org.commonmark.parser.beta.Scanner; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.text.Characters; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java index dd898fcd1..a18966e54 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -3,8 +3,7 @@ import org.commonmark.node.Link; import org.commonmark.node.Text; import org.commonmark.parser.SourceLines; -import org.commonmark.parser.beta.Position; -import org.commonmark.parser.beta.Scanner; +import org.commonmark.parser.beta.*; import java.util.Set; import java.util.regex.Pattern; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java index 70583659f..7baeed4de 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java @@ -3,7 +3,7 @@ import org.commonmark.internal.util.Escaping; import org.commonmark.node.HardLineBreak; import org.commonmark.node.Text; -import org.commonmark.parser.beta.Scanner; +import org.commonmark.parser.beta.*; import java.util.Set; import java.util.regex.Pattern; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java index 75025411c..b8e8984e8 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -3,8 +3,7 @@ import org.commonmark.node.Code; import org.commonmark.node.Text; import org.commonmark.parser.SourceLines; -import org.commonmark.parser.beta.Position; -import org.commonmark.parser.beta.Scanner; +import org.commonmark.parser.beta.*; import org.commonmark.text.Characters; import java.util.Set; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java index 6c226c0a5..c24e60747 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java @@ -2,8 +2,7 @@ import org.commonmark.internal.util.Html5Entities; import org.commonmark.node.Text; -import org.commonmark.parser.beta.Position; -import org.commonmark.parser.beta.Scanner; +import org.commonmark.parser.beta.*; import org.commonmark.text.AsciiMatcher; import java.util.Set; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java index 79fdd250b..a48ea5022 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -1,8 +1,7 @@ package org.commonmark.internal.inline; import org.commonmark.node.HtmlInline; -import org.commonmark.parser.beta.Position; -import org.commonmark.parser.beta.Scanner; +import org.commonmark.parser.beta.*; import org.commonmark.text.AsciiMatcher; import java.util.Set; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java deleted file mode 100644 index 7223c1687..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.commonmark.internal.inline; - -import org.commonmark.node.Node; -import org.commonmark.parser.beta.Position; - -public abstract class ParsedInline { - - protected ParsedInline() { - } - - public static ParsedInline none() { - return null; - } - - public static ParsedInline of(Node node, Position position) { - if (node == null) { - throw new NullPointerException("node must not be null"); - } - if (position == null) { - throw new NullPointerException("position must not be null"); - } - return new ParsedInlineImpl(node, position); - } -} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java index 55f9cc4da..a77630610 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java @@ -1,13 +1,14 @@ package org.commonmark.internal.inline; import org.commonmark.node.Node; +import org.commonmark.parser.beta.ParsedInline; import org.commonmark.parser.beta.Position; -public class ParsedInlineImpl extends ParsedInline { +public class ParsedInlineImpl implements ParsedInline { private final Node node; private final Position position; - ParsedInlineImpl(Node node, Position position) { + public ParsedInlineImpl(Node node, Position position) { this.node = node; this.position = position; } diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java index 1c2594033..2b52cb828 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -1,6 +1,6 @@ package org.commonmark.parser; -import org.commonmark.internal.inline.InlineContentParserFactory; +import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.delimiter.DelimiterProcessor; diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index cedc5ac07..6d15a7192 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -5,7 +5,7 @@ import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; import org.commonmark.internal.LinkReferenceDefinitions; -import org.commonmark.internal.inline.InlineContentParserFactory; +import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.node.*; import org.commonmark.parser.block.BlockParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java b/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParser.java similarity index 96% rename from commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java rename to commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParser.java index cab1d467f..bc5c9a54f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParser.java @@ -1,4 +1,4 @@ -package org.commonmark.internal.inline; +package org.commonmark.parser.beta; /** * Parser for a type of inline content. Registered via a {@link InlineContentParserFactory} and created by its diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParserFactory.java b/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParserFactory.java similarity index 94% rename from commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParserFactory.java rename to commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParserFactory.java index a5e1fe592..54a5d7f6f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParserFactory.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParserFactory.java @@ -1,4 +1,4 @@ -package org.commonmark.internal.inline; +package org.commonmark.parser.beta; import java.util.Set; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java b/commonmark/src/main/java/org/commonmark/parser/beta/InlineParserState.java similarity index 75% rename from commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java rename to commonmark/src/main/java/org/commonmark/parser/beta/InlineParserState.java index ba7369617..e434d45d6 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/InlineParserState.java @@ -1,7 +1,4 @@ -package org.commonmark.internal.inline; - -import org.commonmark.parser.beta.Position; -import org.commonmark.parser.beta.Scanner; +package org.commonmark.parser.beta; public interface InlineParserState { diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/ParsedInline.java b/commonmark/src/main/java/org/commonmark/parser/beta/ParsedInline.java new file mode 100644 index 000000000..5d1402cae --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/beta/ParsedInline.java @@ -0,0 +1,24 @@ +package org.commonmark.parser.beta; + +import org.commonmark.internal.inline.ParsedInlineImpl; +import org.commonmark.node.Node; + +import java.util.Objects; + +/** + * The result of a single inline parser. Use the static methods to create instances. + *

      + * This interface is not intended to be implemented by clients. + */ +public interface ParsedInline { + + static ParsedInline none() { + return null; + } + + static ParsedInline of(Node node, Position position) { + Objects.requireNonNull(node, "node must not be null"); + Objects.requireNonNull(position, "position must not be null"); + return new ParsedInlineImpl(node, position); + } +} diff --git a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java index 212c9a25d..e54aebb26 100644 --- a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java @@ -1,9 +1,9 @@ package org.commonmark.parser; -import org.commonmark.internal.inline.InlineContentParser; -import org.commonmark.internal.inline.InlineContentParserFactory; -import org.commonmark.internal.inline.InlineParserState; -import org.commonmark.internal.inline.ParsedInline; +import org.commonmark.parser.beta.InlineContentParser; +import org.commonmark.parser.beta.InlineContentParserFactory; +import org.commonmark.parser.beta.InlineParserState; +import org.commonmark.parser.beta.ParsedInline; import org.commonmark.node.CustomNode; import org.commonmark.node.Heading; import org.commonmark.test.Nodes; diff --git a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java index e983870c1..9fa7fb0da 100644 --- a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java +++ b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java @@ -1,7 +1,7 @@ package org.commonmark.test; import org.commonmark.internal.InlineParserImpl; -import org.commonmark.internal.inline.InlineContentParserFactory; +import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; From d876efe7e10a242c1129f042f3049ceefdb95c53 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 26 Apr 2024 17:47:43 +1000 Subject: [PATCH 086/345] Rename Parser.Builder method to align with type --- .../java/org/commonmark/parser/InlineParserContext.java | 8 +++++--- .../src/main/java/org/commonmark/parser/Parser.java | 4 ++-- .../org/commonmark/parser/InlineContentParserTest.java | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java index 2b52cb828..dde86b311 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -1,7 +1,7 @@ package org.commonmark.parser; -import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; import java.util.List; @@ -12,12 +12,14 @@ public interface InlineParserContext { /** - * @return custom inline content parsers that have been configured with {@link Parser.Builder#customInlineContentParser(InlineContentParserFactory)} + * @return custom inline content parsers that have been configured with + * {@link Parser.Builder#customInlineContentParserFactory(InlineContentParserFactory)} */ List getCustomInlineContentParserFactories(); /** - * @return custom delimiter processors that have been configured with {@link Parser.Builder#customDelimiterProcessor(DelimiterProcessor)} + * @return custom delimiter processors that have been configured with + * {@link Parser.Builder#customDelimiterProcessor(DelimiterProcessor)} */ List getCustomDelimiterProcessors(); diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 6d15a7192..8d9eb3376 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -5,8 +5,8 @@ import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; import org.commonmark.internal.LinkReferenceDefinitions; -import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.node.*; +import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.block.BlockParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -226,7 +226,7 @@ public Builder customBlockParserFactory(BlockParserFactory blockParserFactory) { * character, or even for some built-in special character such as {@code `}. The custom parsers are tried first * in order in which they are registered, and then the built-in ones. */ - public Builder customInlineContentParser(InlineContentParserFactory inlineContentParserFactory) { + public Builder customInlineContentParserFactory(InlineContentParserFactory inlineContentParserFactory) { Objects.requireNonNull(inlineContentParserFactory, "inlineContentParser must not be null"); inlineContentParserFactories.add(inlineContentParserFactory); return this; diff --git a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java index e54aebb26..28e9b5748 100644 --- a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java @@ -1,11 +1,11 @@ package org.commonmark.parser; +import org.commonmark.node.CustomNode; +import org.commonmark.node.Heading; import org.commonmark.parser.beta.InlineContentParser; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.beta.InlineParserState; import org.commonmark.parser.beta.ParsedInline; -import org.commonmark.node.CustomNode; -import org.commonmark.node.Heading; import org.commonmark.test.Nodes; import org.junit.Test; @@ -17,7 +17,7 @@ public class InlineContentParserTest { @Test public void customInlineContentParser() { - var parser = Parser.builder().customInlineContentParser(new DollarInlineParser.Factory()).build(); + var parser = Parser.builder().customInlineContentParserFactory(new DollarInlineParser.Factory()).build(); var doc = parser.parse("Test: $hey *there*$ $you$\n\n# Heading $heading$\n"); var inline1 = Nodes.find(doc, DollarInline.class); assertEquals("hey *there*", inline1.getLiteral()); From f48193550ae148e18afe1f8e3ce46d3c4e2823b8 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 26 Apr 2024 17:53:18 +1000 Subject: [PATCH 087/345] Add CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index df6beb202..2fc29ab3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html with the exception that 0.x versions can break between minor versions. ## Unreleased +### Added +- Support for extending inline parsing with custom inline content parsers! See + `Parser.Builder#customInlineContentParserFactory`. This allows users or + extensions to hook into inline parsing on a deeper level than using delimiter + processors. It could be used to implement support for math/latex formulas for + example. ### Fixed - Fix parsing of link reference definitions where it looks like it has a title but it doesn't because it's followed by characters other than space/tab. In that From 0fd2427f4061b5dedfb4f9aff8b7bd0797da9d4f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 26 Apr 2024 19:02:49 +1000 Subject: [PATCH 088/345] Add some more Javadoc with links --- commonmark/src/main/java/org/commonmark/parser/Parser.java | 7 +++++-- .../commonmark/parser/beta/InlineContentParserFactory.java | 5 +++++ .../commonmark/parser/delimiter/DelimiterProcessor.java | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 8d9eb3376..febe05b7c 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -219,7 +219,7 @@ public Builder customBlockParserFactory(BlockParserFactory blockParserFactory) { } /** - * Add a factory for a custom inline content parser, for additional inline parsing or overriding built-in parsing. + * Add a factory for a custom inline content parser, for extending inline parsing or overriding built-in parsing. *

      * Note that parsers are triggered based on a special character as specified by * {@link InlineContentParserFactory#getTriggerCharacters()}. It is possible to register multiple parsers for the same @@ -233,11 +233,14 @@ public Builder customInlineContentParserFactory(InlineContentParserFactory inlin } /** - * Add a custom delimiter processor. + * Add a custom delimiter processor for inline parsing. *

      * Note that multiple delimiter processors with the same characters can be added, as long as they have a * different minimum length. In that case, the processor with the shortest matching length is used. Adding more * than one delimiter processor with the same character and minimum length is invalid. + *

      + * If you want more control over how parsing is done, you might want to use + * {@link #customInlineContentParserFactory} instead. * * @param delimiterProcessor a delimiter processor implementation * @return {@code this} diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParserFactory.java b/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParserFactory.java index 54a5d7f6f..c86f93a41 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParserFactory.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParserFactory.java @@ -2,6 +2,11 @@ import java.util.Set; +/** + * A factory for extending inline content parsing. + *

      + * See {@link org.commonmark.parser.Parser.Builder#customInlineContentParserFactory} for how to register it. + */ public interface InlineContentParserFactory { /** diff --git a/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java index 897943d66..3b6abf214 100644 --- a/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java @@ -6,6 +6,8 @@ * Custom delimiter processor for additional delimiters besides {@code _} and {@code *}. *

      * Note that implementations of this need to be thread-safe, the same instance may be used by multiple parsers. + * + * @see org.commonmark.parser.beta.InlineContentParserFactory */ public interface DelimiterProcessor { From 6b16c69326710732d0ba94520c875b9798e30039 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 26 Apr 2024 19:03:18 +1000 Subject: [PATCH 089/345] README: Add section about customizing parsing --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index e4b07bfdc..bcf587f54 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,19 @@ elements in the resulting HTML, you can create your own subclass of To define the HTML rendering for them, you can use a `NodeRenderer` as explained above. +#### Customize parsing + +There are a few ways to extend parsing or even override built-in parsing, +all of them via methods on `Parser.Builder` +(see [Blocks and inlines](https://spec.commonmark.org/0.31.2/#blocks-and-inlines) in the spec for an overview of blocks/inlines): + +- Parsing of specific block types (e.g. headings, code blocks, etc) can be + enabled/disabled with `enabledBlockTypes` +- Parsing of blocks can be extended/overridden with `customBlockParserFactory` +- Parsing of inline content can be extended/overridden with `customInlineContentParserFactory` +- Parsing of [delimiters](https://spec.commonmark.org/0.31.2/#emphasis-and-strong-emphasis) in inline content can be + extended with `customDelimiterProcessor` + #### Thread-safety Both the `Parser` and `HtmlRenderer` are designed so that you can From 3038d47b71affe081e4357fda3b779755c932552 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 26 Apr 2024 22:36:18 +1000 Subject: [PATCH 090/345] Make LinkReferenceDefinition extend Block See https://github.com/commonmark/commonmark-java/issues/315#issuecomment-2040970376 --- CHANGELOG.md | 3 +++ .../main/java/org/commonmark/node/LinkReferenceDefinition.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fc29ab3a..5b4afae55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ with the exception that 0.x versions can break between minor versions. extensions to hook into inline parsing on a deeper level than using delimiter processors. It could be used to implement support for math/latex formulas for example. +### Changed +- `LinkReferenceDefinition` now extends `Block` (it was extending `Node` + directly before) ### Fixed - Fix parsing of link reference definitions where it looks like it has a title but it doesn't because it's followed by characters other than space/tab. In that diff --git a/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java b/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java index 3f8bfd0f0..e63b0242a 100644 --- a/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java +++ b/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java @@ -11,7 +11,7 @@ * * @see Link reference definitions */ -public class LinkReferenceDefinition extends Node { +public class LinkReferenceDefinition extends Block { private String label; private String destination; From 6c9e54ff1b508458c926bded28500c657a37eb27 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 26 Apr 2024 23:03:26 +1000 Subject: [PATCH 091/345] Use List.of, Set.of, Map.of --- README.md | 4 +- .../commonmark/ext/autolink/AutolinkTest.java | 20 ++++---- .../strikethrough/StrikethroughExtension.java | 3 +- .../StrikethroughHtmlNodeRenderer.java | 7 ++- .../internal/StrikethroughNodeRenderer.java | 3 +- .../StrikethroughMarkdownRendererTest.java | 3 +- .../strikethrough/StrikethroughSpecTest.java | 3 +- .../gfm/strikethrough/StrikethroughTest.java | 9 ++-- .../ext/gfm/tables/TablesExtension.java | 3 +- .../internal/TableHtmlNodeRenderer.java | 7 ++- .../tables/internal/TableNodeRenderer.java | 16 ++---- .../gfm/tables/TableMarkdownRendererTest.java | 3 +- .../ext/gfm/tables/TablesSpecTest.java | 4 +- .../commonmark/ext/gfm/tables/TablesTest.java | 49 +++++++++---------- .../ext/gfm/tables/TablesTextContentTest.java | 3 +- .../HeadingAnchorConfigurationTest.java | 6 +-- .../ext/heading/anchor/HeadingAnchorTest.java | 3 +- .../ImageAttributesDelimiterProcessor.java | 3 +- .../image/attributes/ImageAttributesTest.java | 7 ++- .../org/commonmark/ext/ins/InsExtension.java | 3 +- .../ext/ins/internal/InsHtmlNodeRenderer.java | 3 +- .../ext/ins/internal/InsNodeRenderer.java | 3 +- .../ext/ins/InsMarkdownRendererTest.java | 3 +- .../java/org/commonmark/ext/ins/InsTest.java | 7 ++- .../TaskListItemHtmlNodeRenderer.java | 3 +- .../task/list/items/TaskListItemsTest.java | 3 +- .../ext/front/matter/YamlFrontMatterTest.java | 9 ++-- .../commonmark/integration/Extensions.java | 3 +- .../MarkdownRendererIntegrationTest.java | 3 +- .../integration/PegDownBenchmark.java | 3 +- .../commonmark/testutil/TestResources.java | 3 +- .../commonmark/internal/DocumentParser.java | 2 +- .../main/java/org/commonmark/node/Node.java | 2 +- .../java/org/commonmark/node/SourceSpans.java | 3 +- .../java/org/commonmark/parser/Parser.java | 2 +- .../renderer/html/CoreHtmlNodeRenderer.java | 12 +++-- .../renderer/html/DefaultUrlSanitizer.java | 7 +-- .../commonmark/renderer/html/HtmlWriter.java | 3 +- .../markdown/CoreMarkdownNodeRenderer.java | 14 +++--- .../renderer/markdown/MarkdownRenderer.java | 2 +- .../text/CoreTextContentNodeRenderer.java | 10 ++-- .../java/org/commonmark/ProfilingMain.java | 3 +- .../internal/DocumentParserTest.java | 14 +++--- .../commonmark/parser/beta/ScannerTest.java | 17 +++---- .../test/DelimiterProcessorTest.java | 3 +- .../org/commonmark/test/HtmlRendererTest.java | 7 ++- .../test/InlineParserContextTest.java | 3 +- .../java/org/commonmark/test/ParserTest.java | 7 ++- .../org/commonmark/test/SourceSpansTest.java | 17 +++---- .../org/commonmark/test/SpecBenchmark.java | 5 +- .../org/commonmark/test/UsageExampleTest.java | 3 +- 51 files changed, 144 insertions(+), 194 deletions(-) diff --git a/README.md b/README.md index bcf587f54..e2acc08b7 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ class IndentedCodeBlockNodeRenderer implements NodeRenderer { @Override public Set> getNodeTypes() { // Return the node types we want to use this renderer for. - return Collections.>singleton(IndentedCodeBlock.class); + return Set.of(IndentedCodeBlock.class); } @Override @@ -274,7 +274,7 @@ Then, configure the extension on the builders: ```java import org.commonmark.ext.gfm.tables.TablesExtension; -List extensions = Arrays.asList(TablesExtension.create()); +List extensions = List.of(TablesExtension.create()); Parser parser = Parser.builder() .extensions(extensions) .build(); diff --git a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java index 2a9fd3b69..6c4c18d0a 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -8,17 +8,15 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; -import java.util.Arrays; -import java.util.Collections; +import java.util.List; import java.util.Set; -import static org.hamcrest.CoreMatchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class AutolinkTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(AutolinkExtension.create()); + private static final Set EXTENSIONS = Set.of(AutolinkExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); @@ -73,43 +71,43 @@ public void sourceSpans() { Paragraph paragraph = (Paragraph) document.getFirstChild(); Text abc = (Text) paragraph.getFirstChild(); - assertEquals(Arrays.asList(SourceSpan.of(0, 0, 3)), + assertEquals(List.of(SourceSpan.of(0, 0, 3)), abc.getSourceSpans()); assertTrue(abc.getNext() instanceof SoftLineBreak); Link one = (Link) abc.getNext().getNext(); assertEquals("http://example.com/one", one.getDestination()); - assertEquals(Arrays.asList(SourceSpan.of(1, 0, 22)), + assertEquals(List.of(SourceSpan.of(1, 0, 22)), one.getSourceSpans()); assertTrue(one.getNext() instanceof SoftLineBreak); Text def = (Text) one.getNext().getNext(); assertEquals("def ", def.getLiteral()); - assertEquals(Arrays.asList(SourceSpan.of(2, 0, 4)), + assertEquals(List.of(SourceSpan.of(2, 0, 4)), def.getSourceSpans()); Link two = (Link) def.getNext(); assertEquals("http://example.com/two", two.getDestination()); - assertEquals(Arrays.asList(SourceSpan.of(2, 4, 22)), + assertEquals(List.of(SourceSpan.of(2, 4, 22)), two.getSourceSpans()); assertTrue(two.getNext() instanceof SoftLineBreak); Text ghi = (Text) two.getNext().getNext(); assertEquals("ghi ", ghi.getLiteral()); - assertEquals(Arrays.asList(SourceSpan.of(3, 0, 4)), + assertEquals(List.of(SourceSpan.of(3, 0, 4)), ghi.getSourceSpans()); Link three = (Link) ghi.getNext(); assertEquals("http://example.com/three", three.getDestination()); - assertEquals(Arrays.asList(SourceSpan.of(3, 4, 24)), + assertEquals(List.of(SourceSpan.of(3, 4, 24)), three.getSourceSpans()); Text jkl = (Text) three.getNext(); assertEquals(" jkl", jkl.getLiteral()); - assertEquals(Arrays.asList(SourceSpan.of(3, 28, 4)), + assertEquals(List.of(SourceSpan.of(3, 28, 4)), jkl.getSourceSpans()); } diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java index f87f3e9c8..364205aed 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java @@ -17,7 +17,6 @@ import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentRenderer; -import java.util.Collections; import java.util.Set; /** @@ -106,7 +105,7 @@ public NodeRenderer create(MarkdownNodeRendererContext context) { @Override public Set getSpecialCharacters() { - return Collections.singleton('~'); + return Set.of('~'); } }); } diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlNodeRenderer.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlNodeRenderer.java index 4dd0de39b..b1a82cb03 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlNodeRenderer.java @@ -1,10 +1,9 @@ package org.commonmark.ext.gfm.strikethrough.internal; -import org.commonmark.renderer.html.HtmlWriter; -import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.node.Node; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlWriter; -import java.util.Collections; import java.util.Map; public class StrikethroughHtmlNodeRenderer extends StrikethroughNodeRenderer { @@ -19,7 +18,7 @@ public StrikethroughHtmlNodeRenderer(HtmlNodeRendererContext context) { @Override public void render(Node node) { - Map attributes = context.extendAttributes(node, "del", Collections.emptyMap()); + Map attributes = context.extendAttributes(node, "del", Map.of()); html.tag("del", attributes); renderChildren(node); html.tag("/del"); diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughNodeRenderer.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughNodeRenderer.java index 4f3a12618..18ed21887 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughNodeRenderer.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughNodeRenderer.java @@ -4,13 +4,12 @@ import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; -import java.util.Collections; import java.util.Set; abstract class StrikethroughNodeRenderer implements NodeRenderer { @Override public Set> getNodeTypes() { - return Collections.>singleton(Strikethrough.class); + return Set.of(Strikethrough.class); } } diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java index 96df48cec..507fc0a88 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java @@ -5,14 +5,13 @@ import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.Test; -import java.util.Collections; import java.util.Set; import static org.junit.Assert.assertEquals; public class StrikethroughMarkdownRendererTest { - private static final Set EXTENSIONS = Collections.singleton(StrikethroughExtension.create()); + private static final Set EXTENSIONS = Set.of(StrikethroughExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java index 4b907cf41..2ca5471b0 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java @@ -12,14 +12,13 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import java.util.Collections; import java.util.List; import java.util.Set; @RunWith(Parameterized.class) public class StrikethroughSpecTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(StrikethroughExtension.create()); + private static final Set EXTENSIONS = Set.of(StrikethroughExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java index d8a754c72..de0a347f9 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java @@ -14,15 +14,14 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; -import java.util.Arrays; +import java.util.List; import java.util.Set; -import static java.util.Collections.singleton; import static org.junit.Assert.assertEquals; public class StrikethroughTest extends RenderingTestCase { - private static final Set EXTENSIONS = singleton(StrikethroughExtension.create()); + private static final Set EXTENSIONS = Set.of(StrikethroughExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer HTML_RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); private static final TextContentRenderer CONTENT_RENDERER = TextContentRenderer.builder() @@ -98,7 +97,7 @@ public void textContentRenderer() { @Test public void requireTwoTildesOption() { Parser parser = Parser.builder() - .extensions(singleton(StrikethroughExtension.builder() + .extensions(Set.of(StrikethroughExtension.builder() .requireTwoTildes(true) .build())) .customDelimiterProcessor(new SubscriptDelimiterProcessor()) @@ -118,7 +117,7 @@ public void sourceSpans() { Node document = parser.parse("hey ~~there~~\n"); Paragraph block = (Paragraph) document.getFirstChild(); Node strikethrough = block.getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(0, 4, 9)), + assertEquals(List.of(SourceSpan.of(0, 4, 9)), strikethrough.getSourceSpans()); } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java index d18c38283..f754b8276 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java @@ -17,7 +17,6 @@ import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentRenderer; -import java.util.Collections; import java.util.Set; /** @@ -78,7 +77,7 @@ public NodeRenderer create(MarkdownNodeRendererContext context) { @Override public Set getSpecialCharacters() { - return Collections.singleton('|'); + return Set.of('|'); } }); } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java index a1de50aac..fd07e84df 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java @@ -5,7 +5,6 @@ import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; -import java.util.Collections; import java.util.Map; public class TableHtmlNodeRenderer extends TableNodeRenderer { @@ -60,14 +59,14 @@ protected void renderCell(TableCell tableCell) { } private Map getAttributes(Node node, String tagName) { - return context.extendAttributes(node, tagName, Collections.emptyMap()); + return context.extendAttributes(node, tagName, Map.of()); } private Map getCellAttributes(TableCell tableCell, String tagName) { if (tableCell.getAlignment() != null) { - return context.extendAttributes(tableCell, tagName, Collections.singletonMap("align", getAlignValue(tableCell.getAlignment()))); + return context.extendAttributes(tableCell, tagName, Map.of("align", getAlignValue(tableCell.getAlignment()))); } else { - return context.extendAttributes(tableCell, tagName, Collections.emptyMap()); + return context.extendAttributes(tableCell, tagName, Map.of()); } } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java index 93478a30b..2982e1518 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java @@ -1,28 +1,22 @@ package org.commonmark.ext.gfm.tables.internal; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -import org.commonmark.ext.gfm.tables.TableBlock; -import org.commonmark.ext.gfm.tables.TableBody; -import org.commonmark.ext.gfm.tables.TableCell; -import org.commonmark.ext.gfm.tables.TableHead; -import org.commonmark.ext.gfm.tables.TableRow; +import org.commonmark.ext.gfm.tables.*; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; +import java.util.Set; + abstract class TableNodeRenderer implements NodeRenderer { @Override public Set> getNodeTypes() { - return new HashSet<>(Arrays.asList( + return Set.of( TableBlock.class, TableHead.class, TableBody.class, TableRow.class, TableCell.class - )); + ); } @Override diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java index 1db917d08..16303b58c 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java @@ -5,14 +5,13 @@ import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.Test; -import java.util.Collections; import java.util.Set; import static org.junit.Assert.assertEquals; public class TableMarkdownRendererTest { - private static final Set EXTENSIONS = Collections.singleton(TablesExtension.create()); + private static final Set EXTENSIONS = Set.of(TablesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java index 00fc61401..d5ea8c836 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java @@ -12,15 +12,13 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Set; @RunWith(Parameterized.class) public class TablesSpecTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(TablesExtension.create()); + private static final Set EXTENSIONS = Set.of(TablesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java index b03714d9a..c2a8031d6 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -12,10 +12,7 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; -import java.util.Set; +import java.util.*; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; @@ -23,7 +20,7 @@ public class TablesTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(TablesExtension.create()); + private static final Set EXTENSIONS = Set.of(TablesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); @@ -794,49 +791,49 @@ public void sourceSpans() { Node document = parser.parse("Abc|Def\n---|---\n|1|2\n 3|four|\n|||\n"); TableBlock block = (TableBlock) document.getFirstChild(); - assertEquals(Arrays.asList(SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 7), + assertEquals(List.of(SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 7), SourceSpan.of(2, 0, 4), SourceSpan.of(3, 0, 8), SourceSpan.of(4, 0, 3)), block.getSourceSpans()); TableHead head = (TableHead) block.getFirstChild(); - assertEquals(Arrays.asList(SourceSpan.of(0, 0, 7)), head.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 7)), head.getSourceSpans()); TableRow headRow = (TableRow) head.getFirstChild(); - assertEquals(Arrays.asList(SourceSpan.of(0, 0, 7)), headRow.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 7)), headRow.getSourceSpans()); TableCell headRowCell1 = (TableCell) headRow.getFirstChild(); TableCell headRowCell2 = (TableCell) headRow.getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(0, 0, 3)), headRowCell1.getSourceSpans()); - assertEquals(Arrays.asList(SourceSpan.of(0, 0, 3)), headRowCell1.getFirstChild().getSourceSpans()); - assertEquals(Arrays.asList(SourceSpan.of(0, 4, 3)), headRowCell2.getSourceSpans()); - assertEquals(Arrays.asList(SourceSpan.of(0, 4, 3)), headRowCell2.getFirstChild().getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 3)), headRowCell1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 3)), headRowCell1.getFirstChild().getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 4, 3)), headRowCell2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 4, 3)), headRowCell2.getFirstChild().getSourceSpans()); TableBody body = (TableBody) block.getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(2, 0, 4), SourceSpan.of(3, 0, 8), SourceSpan.of(4, 0, 3)), body.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(2, 0, 4), SourceSpan.of(3, 0, 8), SourceSpan.of(4, 0, 3)), body.getSourceSpans()); TableRow bodyRow1 = (TableRow) body.getFirstChild(); - assertEquals(Arrays.asList(SourceSpan.of(2, 0, 4)), bodyRow1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(2, 0, 4)), bodyRow1.getSourceSpans()); TableCell bodyRow1Cell1 = (TableCell) bodyRow1.getFirstChild(); TableCell bodyRow1Cell2 = (TableCell) bodyRow1.getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(2, 1, 1)), bodyRow1Cell1.getSourceSpans()); - assertEquals(Arrays.asList(SourceSpan.of(2, 1, 1)), bodyRow1Cell1.getFirstChild().getSourceSpans()); - assertEquals(Arrays.asList(SourceSpan.of(2, 3, 1)), bodyRow1Cell2.getSourceSpans()); - assertEquals(Arrays.asList(SourceSpan.of(2, 3, 1)), bodyRow1Cell2.getFirstChild().getSourceSpans()); + assertEquals(List.of(SourceSpan.of(2, 1, 1)), bodyRow1Cell1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(2, 1, 1)), bodyRow1Cell1.getFirstChild().getSourceSpans()); + assertEquals(List.of(SourceSpan.of(2, 3, 1)), bodyRow1Cell2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(2, 3, 1)), bodyRow1Cell2.getFirstChild().getSourceSpans()); TableRow bodyRow2 = (TableRow) body.getFirstChild().getNext(); - assertEquals(Arrays.asList(SourceSpan.of(3, 0, 8)), bodyRow2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(3, 0, 8)), bodyRow2.getSourceSpans()); TableCell bodyRow2Cell1 = (TableCell) bodyRow2.getFirstChild(); TableCell bodyRow2Cell2 = (TableCell) bodyRow2.getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(3, 1, 1)), bodyRow2Cell1.getSourceSpans()); - assertEquals(Arrays.asList(SourceSpan.of(3, 1, 1)), bodyRow2Cell1.getFirstChild().getSourceSpans()); - assertEquals(Arrays.asList(SourceSpan.of(3, 3, 4)), bodyRow2Cell2.getSourceSpans()); - assertEquals(Arrays.asList(SourceSpan.of(3, 3, 4)), bodyRow2Cell2.getFirstChild().getSourceSpans()); + assertEquals(List.of(SourceSpan.of(3, 1, 1)), bodyRow2Cell1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(3, 1, 1)), bodyRow2Cell1.getFirstChild().getSourceSpans()); + assertEquals(List.of(SourceSpan.of(3, 3, 4)), bodyRow2Cell2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(3, 3, 4)), bodyRow2Cell2.getFirstChild().getSourceSpans()); TableRow bodyRow3 = (TableRow) body.getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(4, 0, 3)), bodyRow3.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(4, 0, 3)), bodyRow3.getSourceSpans()); TableCell bodyRow3Cell1 = (TableCell) bodyRow3.getFirstChild(); TableCell bodyRow3Cell2 = (TableCell) bodyRow3.getLastChild(); - assertEquals(Collections.emptyList(), bodyRow3Cell1.getSourceSpans()); - assertEquals(Collections.emptyList(), bodyRow3Cell2.getSourceSpans()); + assertEquals(List.of(), bodyRow3Cell1.getSourceSpans()); + assertEquals(List.of(), bodyRow3Cell2.getSourceSpans()); } @Override diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java index 6d859f1c9..7d6feb248 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java @@ -6,12 +6,11 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; -import java.util.Collections; import java.util.Set; public class TablesTextContentTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(TablesExtension.create()); + private static final Set EXTENSIONS = Set.of(TablesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final TextContentRenderer RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS).build(); diff --git a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java index 5a7f47cd3..af2ae13cf 100644 --- a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java @@ -5,7 +5,7 @@ import org.commonmark.renderer.html.HtmlRenderer; import org.junit.Test; -import java.util.Arrays; +import java.util.List; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; @@ -21,14 +21,14 @@ private HtmlRenderer buildRenderer(String defaultId, String prefix, String suffi .idSuffix(suffix) .build(); return HtmlRenderer.builder() - .extensions(Arrays.asList(ext)) + .extensions(List.of(ext)) .build(); } @Test public void testDefaultConfigurationHasNoAdditions() { HtmlRenderer renderer = HtmlRenderer.builder() - .extensions(Arrays.asList(HeadingAnchorExtension.create())) + .extensions(List.of(HeadingAnchorExtension.create())) .build(); assertThat(doRender(renderer, "# "), equalTo("

      \n")); } diff --git a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java index 821aa9a84..872306bed 100644 --- a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java @@ -6,12 +6,11 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; -import java.util.Collections; import java.util.Set; public class HeadingAnchorTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(HeadingAnchorExtension.create()); + private static final Set EXTENSIONS = Set.of(HeadingAnchorExtension.create()); private static final Parser PARSER = Parser.builder().build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java index a584948e3..a335ccadc 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java @@ -13,8 +13,7 @@ public class ImageAttributesDelimiterProcessor implements DelimiterProcessor { // Only allow a defined set of attributes to be used. - private static final Set SUPPORTED_ATTRIBUTES = Collections.unmodifiableSet( - new HashSet<>(Arrays.asList("width", "height"))); + private static final Set SUPPORTED_ATTRIBUTES = Set.of("width", "height"); @Override public char getOpeningCharacter() { diff --git a/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java b/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java index b7d8a84c3..112bf53d3 100644 --- a/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java +++ b/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java @@ -10,15 +10,14 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; -import java.util.Arrays; -import java.util.Collections; +import java.util.List; import java.util.Set; import static org.junit.Assert.assertEquals; public class ImageAttributesTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(ImageAttributesExtension.create()); + private static final Set EXTENSIONS = Set.of(ImageAttributesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); @@ -132,7 +131,7 @@ public void sourceSpans() { Node document = parser.parse("x{height=3 width=4}\n"); Paragraph block = (Paragraph) document.getFirstChild(); Node text = block.getFirstChild(); - assertEquals(Arrays.asList(SourceSpan.of(0, 0, 19)), + assertEquals(List.of(SourceSpan.of(0, 0, 19)), text.getSourceSpans()); } diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java index 065719558..e8a53e59a 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java @@ -17,7 +17,6 @@ import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentRenderer; -import java.util.Collections; import java.util.Set; /** @@ -77,7 +76,7 @@ public NodeRenderer create(MarkdownNodeRendererContext context) { public Set getSpecialCharacters() { // We technically don't need to escape single occurrences of +, but that's all the extension API // exposes currently. - return Collections.singleton('+'); + return Set.of('+'); } }); } diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsHtmlNodeRenderer.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsHtmlNodeRenderer.java index 139a0b2cd..dcd05fd59 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsHtmlNodeRenderer.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsHtmlNodeRenderer.java @@ -4,7 +4,6 @@ import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; -import java.util.Collections; import java.util.Map; public class InsHtmlNodeRenderer extends InsNodeRenderer { @@ -19,7 +18,7 @@ public InsHtmlNodeRenderer(HtmlNodeRendererContext context) { @Override public void render(Node node) { - Map attributes = context.extendAttributes(node, "ins", Collections.emptyMap()); + Map attributes = context.extendAttributes(node, "ins", Map.of()); html.tag("ins", attributes); renderChildren(node); html.tag("/ins"); diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java index 0a44a2826..31f0a64ec 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java @@ -4,13 +4,12 @@ import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; -import java.util.Collections; import java.util.Set; abstract class InsNodeRenderer implements NodeRenderer { @Override public Set> getNodeTypes() { - return Collections.>singleton(Ins.class); + return Set.of(Ins.class); } } diff --git a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java index 16cefc7f1..28f2cd354 100644 --- a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java +++ b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java @@ -5,14 +5,13 @@ import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.Test; -import java.util.Collections; import java.util.Set; import static org.junit.Assert.assertEquals; public class InsMarkdownRendererTest { - private static final Set EXTENSIONS = Collections.singleton(InsExtension.create()); + private static final Set EXTENSIONS = Set.of(InsExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); diff --git a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java index c9591af5e..0fd8d512a 100644 --- a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java +++ b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java @@ -11,15 +11,14 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; -import java.util.Arrays; -import java.util.Collections; +import java.util.List; import java.util.Set; import static org.junit.Assert.assertEquals; public class InsTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(InsExtension.create()); + private static final Set EXTENSIONS = Set.of(InsExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); private static final TextContentRenderer CONTENT_RENDERER = TextContentRenderer.builder() @@ -103,7 +102,7 @@ public void sourceSpans() { Node document = parser.parse("hey ++there++\n"); Paragraph block = (Paragraph) document.getFirstChild(); Node ins = block.getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(0, 4, 9)), + assertEquals(List.of(SourceSpan.of(0, 4, 9)), ins.getSourceSpans()); } diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java index f2b2215f6..331b301e9 100644 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java @@ -6,7 +6,6 @@ import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -23,7 +22,7 @@ public TaskListItemHtmlNodeRenderer(HtmlNodeRendererContext context) { @Override public Set> getNodeTypes() { - return Collections.>singleton(TaskListItemMarker.class); + return Set.of(TaskListItemMarker.class); } @Override diff --git a/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java index 3e6c6a259..c13e10bb7 100644 --- a/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java +++ b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java @@ -6,12 +6,11 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; -import java.util.Collections; import java.util.Set; public class TaskListItemsTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(TaskListItemsExtension.create()); + private static final Set EXTENSIONS = Set.of(TaskListItemsExtension.create()); private static final String HTML_CHECKED = ""; private static final String HTML_UNCHECKED = ""; private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); diff --git a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java index a5f203b97..f46c11b3c 100644 --- a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java +++ b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java @@ -8,7 +8,6 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -17,7 +16,7 @@ import static org.junit.Assert.assertTrue; public class YamlFrontMatterTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(YamlFrontMatterExtension.create()); + private static final Set EXTENSIONS = Set.of(YamlFrontMatterExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); @@ -243,7 +242,7 @@ public void visitorIgnoresOtherCustomNodes() { Map> data = visitor.getData(); assertEquals(1, data.size()); assertTrue(data.containsKey("hello")); - assertEquals(Collections.singletonList("world"), data.get("hello")); + assertEquals(List.of("world"), data.get("hello")); } @Test @@ -256,7 +255,7 @@ public void nodesCanBeModified() { Node document = PARSER.parse(input); YamlFrontMatterNode node = (YamlFrontMatterNode) document.getFirstChild().getFirstChild(); node.setKey("see"); - node.setValues(Collections.singletonList("you")); + node.setValues(List.of("you")); YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); document.accept(visitor); @@ -264,7 +263,7 @@ public void nodesCanBeModified() { Map> data = visitor.getData(); assertEquals(1, data.size()); assertTrue(data.containsKey("see")); - assertEquals(Collections.singletonList("you"), data.get("see")); + assertEquals(List.of("you"), data.get("see")); } @Test diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java index 5eddcc57a..ee7ee5290 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java @@ -9,12 +9,11 @@ import org.commonmark.ext.ins.InsExtension; import org.commonmark.ext.task.list.items.TaskListItemsExtension; -import java.util.Arrays; import java.util.List; public class Extensions { - static final List ALL_EXTENSIONS = Arrays.asList( + static final List ALL_EXTENSIONS = List.of( AutolinkExtension.create(), ImageAttributesExtension.create(), InsExtension.create(), diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java index 51f761cfd..f05efe1c3 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java @@ -12,14 +12,13 @@ import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.Test; -import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertEquals; public class MarkdownRendererIntegrationTest { - private static final List EXTENSIONS = Arrays.asList( + private static final List EXTENSIONS = List.of( AutolinkExtension.create(), ImageAttributesExtension.create(), InsExtension.create(), diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java index 7b61242f4..ecc9c2cfd 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java @@ -12,7 +12,6 @@ import org.pegdown.Extensions; import org.pegdown.PegDownProcessor; -import java.util.Collections; import java.util.List; @State(Scope.Benchmark) @@ -32,7 +31,7 @@ public static void main(String[] args) throws Exception { @Benchmark public long wholeSpec() { - return parseAndRender(Collections.singletonList(SPEC)); + return parseAndRender(List.of(SPEC)); } @Benchmark diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java index ac4d3f1c2..3012f2714 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java @@ -5,7 +5,6 @@ import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.Charset; -import java.util.Arrays; import java.util.List; public class TestResources { @@ -19,7 +18,7 @@ public static URL getGfmSpec() { } public static List getRegressions() { - return Arrays.asList( + return List.of( TestResources.class.getResource("/cmark-regression.txt"), TestResources.class.getResource("/commonmark.js-regression.txt") ); diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 6884c56a9..e8c0f837b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -15,7 +15,7 @@ public class DocumentParser implements ParserState { - private static final Set> CORE_FACTORY_TYPES = new LinkedHashSet<>(Arrays.asList( + private static final Set> CORE_FACTORY_TYPES = new LinkedHashSet<>(List.of( BlockQuote.class, Heading.class, FencedCodeBlock.class, diff --git a/commonmark/src/main/java/org/commonmark/node/Node.java b/commonmark/src/main/java/org/commonmark/node/Node.java index 5a2f036e4..4f806d550 100644 --- a/commonmark/src/main/java/org/commonmark/node/Node.java +++ b/commonmark/src/main/java/org/commonmark/node/Node.java @@ -120,7 +120,7 @@ public void insertBefore(Node sibling) { * @since 0.16.0 */ public List getSourceSpans() { - return sourceSpans != null ? Collections.unmodifiableList(sourceSpans) : Collections.emptyList(); + return sourceSpans != null ? Collections.unmodifiableList(sourceSpans) : List.of(); } /** diff --git a/commonmark/src/main/java/org/commonmark/node/SourceSpans.java b/commonmark/src/main/java/org/commonmark/node/SourceSpans.java index 3ab29f536..5118dea75 100644 --- a/commonmark/src/main/java/org/commonmark/node/SourceSpans.java +++ b/commonmark/src/main/java/org/commonmark/node/SourceSpans.java @@ -1,7 +1,6 @@ package org.commonmark.node; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -18,7 +17,7 @@ public static SourceSpans empty() { } public List getSourceSpans() { - return sourceSpans != null ? sourceSpans : Collections.emptyList(); + return sourceSpans != null ? sourceSpans : List.of(); } public void addAllFrom(Iterable nodes) { diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index febe05b7c..129110b99 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -169,7 +169,7 @@ public Builder extensions(Iterable extensions) { * E.g., to only parse headings and lists: *
                *     {@code
      -         *     Parser.builder().enabledBlockTypes(new HashSet<>(Arrays.asList(Heading.class, ListBlock.class)));
      +         *     Parser.builder().enabledBlockTypes(Set.of(Heading.class, ListBlock.class));
                *     }
                * 
      * diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java index 47343b53c..115d553f0 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -3,7 +3,9 @@ import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; -import java.util.*; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; /** * The node renderer that renders all the core nodes (comes last in the order of node renderers). @@ -20,7 +22,7 @@ public CoreHtmlNodeRenderer(HtmlNodeRendererContext context) { @Override public Set> getNodeTypes() { - return new HashSet<>(Arrays.asList( + return Set.of( Document.class, Heading.class, Paragraph.class, @@ -41,7 +43,7 @@ public Set> getNodeTypes() { HtmlInline.class, SoftLineBreak.class, HardLineBreak.class - )); + ); } @Override @@ -135,7 +137,7 @@ public void visit(ThematicBreak thematicBreak) { @Override public void visit(IndentedCodeBlock indentedCodeBlock) { - renderCodeBlock(indentedCodeBlock.getLiteral(), indentedCodeBlock, Collections.emptyMap()); + renderCodeBlock(indentedCodeBlock.getLiteral(), indentedCodeBlock, Map.of()); } @Override @@ -287,7 +289,7 @@ private boolean isInTightList(Paragraph paragraph) { } private Map getAttrs(Node node, String tagName) { - return getAttrs(node, tagName, Collections.emptyMap()); + return getAttrs(node, tagName, Map.of()); } private Map getAttrs(Node node, String tagName, Map defaultAttributes) { diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java b/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java index 6cc96c5e7..032b8ef2e 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java @@ -1,9 +1,6 @@ package org.commonmark.renderer.html; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import java.util.*; /** * @@ -15,7 +12,7 @@ public class DefaultUrlSanitizer implements UrlSanitizer { private Set protocols; public DefaultUrlSanitizer() { - this(Arrays.asList("http", "https", "mailto")); + this(List.of("http", "https", "mailto")); } public DefaultUrlSanitizer(Collection protocols) { diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java index 8c79eb8b4..2b4b2896d 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java @@ -3,12 +3,11 @@ import org.commonmark.internal.util.Escaping; import java.io.IOException; -import java.util.Collections; import java.util.Map; public class HtmlWriter { - private static final Map NO_ATTRIBUTES = Collections.emptyMap(); + private static final Map NO_ATTRIBUTES = Map.of(); private final Appendable buffer; private char lastChar = 0; diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 229d9d262..8a9e57251 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -1,13 +1,11 @@ package org.commonmark.renderer.markdown; -import org.commonmark.text.AsciiMatcher; import org.commonmark.node.*; -import org.commonmark.text.CharMatcher; import org.commonmark.renderer.NodeRenderer; +import org.commonmark.text.AsciiMatcher; +import org.commonmark.text.CharMatcher; import org.commonmark.text.Characters; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; @@ -51,7 +49,7 @@ public CoreMarkdownNodeRenderer(MarkdownNodeRendererContext context) { @Override public Set> getNodeTypes() { - return new HashSet<>(Arrays.asList( + return Set.of( BlockQuote.class, BulletList.class, Code.class, @@ -72,7 +70,7 @@ public Set> getNodeTypes() { StrongEmphasis.class, Text.class, ThematicBreak.class - )); + ); } @Override @@ -470,9 +468,9 @@ private static List getLines(String literal) { if (parts[parts.length - 1].isEmpty()) { // But we don't want the last empty string, as "\n" is used as a line terminator (not a separator), // so return without the last element. - return Arrays.asList(parts).subList(0, parts.length - 1); + return List.of(parts).subList(0, parts.length - 1); } else { - return Arrays.asList(parts); + return List.of(parts); } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java index 2ee89ea1a..7683b6921 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java @@ -38,7 +38,7 @@ public NodeRenderer create(MarkdownNodeRendererContext context) { @Override public Set getSpecialCharacters() { - return Collections.emptySet(); + return Set.of(); } }); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java index a5f9db518..6dcc9d1eb 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -1,13 +1,11 @@ package org.commonmark.renderer.text; -import org.commonmark.node.*; -import org.commonmark.renderer.NodeRenderer; import org.commonmark.internal.renderer.text.BulletListHolder; import org.commonmark.internal.renderer.text.ListHolder; import org.commonmark.internal.renderer.text.OrderedListHolder; +import org.commonmark.node.*; +import org.commonmark.renderer.NodeRenderer; -import java.util.Arrays; -import java.util.HashSet; import java.util.Set; /** @@ -27,7 +25,7 @@ public CoreTextContentNodeRenderer(TextContentNodeRendererContext context) { @Override public Set> getNodeTypes() { - return new HashSet<>(Arrays.asList( + return Set.of( Document.class, Heading.class, Paragraph.class, @@ -48,7 +46,7 @@ public Set> getNodeTypes() { HtmlInline.class, SoftLineBreak.class, HardLineBreak.class - )); + ); } @Override diff --git a/commonmark/src/test/java/org/commonmark/ProfilingMain.java b/commonmark/src/test/java/org/commonmark/ProfilingMain.java index 31ae2b5f5..83b1bdaff 100644 --- a/commonmark/src/test/java/org/commonmark/ProfilingMain.java +++ b/commonmark/src/test/java/org/commonmark/ProfilingMain.java @@ -6,7 +6,6 @@ import org.commonmark.testutil.TestResources; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public class ProfilingMain { @@ -20,7 +19,7 @@ public static void main(String[] args) throws Exception { System.out.println("Attach profiler, then press enter to start parsing."); System.in.read(); System.out.println("Parsing"); - List nodes = parse(Collections.singletonList(SPEC)); + List nodes = parse(List.of(SPEC)); System.out.println("Finished parsing, press enter to start rendering"); System.in.read(); System.out.println(render(nodes)); diff --git a/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java b/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java index c4d848362..621bef25b 100644 --- a/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java @@ -4,18 +4,16 @@ import org.commonmark.parser.block.BlockParserFactory; import org.junit.Test; -import java.util.Arrays; -import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.HashSet; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; public class DocumentParserTest { - private static List CORE_FACTORIES = Arrays.asList( + private static final List CORE_FACTORIES = List.of( new BlockQuoteParser.Factory(), new HeadingParser.Factory(), new FencedCodeBlockParser.Factory(), @@ -26,10 +24,10 @@ public class DocumentParserTest { @Test public void calculateBlockParserFactories_givenAFullListOfAllowedNodes_includesAllCoreFactories() { - List customParserFactories = Collections.emptyList(); - Set> nodes = new HashSet<>(Arrays.asList(BlockQuote.class, Heading.class, FencedCodeBlock.class, HtmlBlock.class, ThematicBreak.class, ListBlock.class, IndentedCodeBlock.class)); + List customParserFactories = List.of(); + var enabledBlockTypes = Set.of(BlockQuote.class, Heading.class, FencedCodeBlock.class, HtmlBlock.class, ThematicBreak.class, ListBlock.class, IndentedCodeBlock.class); - List blockParserFactories = DocumentParser.calculateBlockParserFactories(customParserFactories, nodes); + List blockParserFactories = DocumentParser.calculateBlockParserFactories(customParserFactories, enabledBlockTypes); assertThat(blockParserFactories.size(), is(CORE_FACTORIES.size())); for (BlockParserFactory factory : CORE_FACTORIES) { @@ -39,7 +37,7 @@ public void calculateBlockParserFactories_givenAFullListOfAllowedNodes_includesA @Test public void calculateBlockParserFactories_givenAListOfAllowedNodes_includesAssociatedFactories() { - List customParserFactories = Collections.emptyList(); + List customParserFactories = List.of(); Set> nodes = new HashSet<>(); nodes.add(IndentedCodeBlock.class); diff --git a/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java b/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java index 668f852c4..df8c51758 100644 --- a/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java @@ -3,12 +3,9 @@ import org.commonmark.node.SourceSpan; import org.commonmark.parser.SourceLine; import org.commonmark.parser.SourceLines; -import org.commonmark.parser.beta.Position; -import org.commonmark.parser.beta.Scanner; import org.junit.Test; -import java.util.Arrays; -import java.util.Collections; +import java.util.List; import static org.junit.Assert.*; @@ -16,7 +13,7 @@ public class ScannerTest { @Test public void testNext() { - Scanner scanner = new Scanner(Collections.singletonList( + Scanner scanner = new Scanner(List.of( SourceLine.of("foo bar", null)), 0, 4); assertEquals('b', scanner.peek()); @@ -30,7 +27,7 @@ public void testNext() { @Test public void testMultipleLines() { - Scanner scanner = new Scanner(Arrays.asList( + Scanner scanner = new Scanner(List.of( SourceLine.of("ab", null), SourceLine.of("cde", null)), 0, 0); @@ -71,7 +68,7 @@ public void testMultipleLines() { @Test public void testCodePoints() { - Scanner scanner = new Scanner(Arrays.asList(SourceLine.of("\uD83D\uDE0A", null)), 0, 0); + Scanner scanner = new Scanner(List.of(SourceLine.of("\uD83D\uDE0A", null)), 0, 0); assertTrue(scanner.hasNext()); assertEquals('\0', scanner.peekPreviousCodePoint()); @@ -87,7 +84,7 @@ public void testCodePoints() { @Test public void testTextBetween() { - Scanner scanner = new Scanner(Arrays.asList( + Scanner scanner = new Scanner(List.of( SourceLine.of("ab", SourceSpan.of(10, 3, 2)), SourceLine.of("cde", SourceSpan.of(11, 4, 3))), 0, 0); @@ -143,12 +140,12 @@ public void testTextBetween() { private void assertSourceLines(SourceLines sourceLines, String expectedContent, SourceSpan... expectedSourceSpans) { assertEquals(expectedContent, sourceLines.getContent()); - assertEquals(Arrays.asList(expectedSourceSpans), sourceLines.getSourceSpans()); + assertEquals(List.of(expectedSourceSpans), sourceLines.getSourceSpans()); } @Test public void nextString() { - Scanner scanner = Scanner.of(SourceLines.of(Arrays.asList( + Scanner scanner = Scanner.of(SourceLines.of(List.of( SourceLine.of("hey ya", null), SourceLine.of("hi", null)))); assertFalse(scanner.next("hoy")); diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index d2e20a64f..680c40bf2 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -13,7 +13,6 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; -import java.util.Collections; import java.util.Locale; import java.util.Set; @@ -159,7 +158,7 @@ private UpperCaseNodeRenderer(HtmlNodeRendererContext context) { @Override public Set> getNodeTypes() { - return Collections.>singleton(UpperCaseNode.class); + return Set.of(UpperCaseNode.class); } @Override diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 18ce967b2..7aec21ceb 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -7,7 +7,10 @@ import org.commonmark.testutil.TestResources; import org.junit.Test; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -214,7 +217,7 @@ public NodeRenderer create(final HtmlNodeRendererContext context) { return new NodeRenderer() { @Override public Set> getNodeTypes() { - return Collections.>singleton(Link.class); + return Set.of(Link.class); } @Override diff --git a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java index 9fa7fb0da..26a7da559 100644 --- a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java +++ b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java @@ -12,7 +12,6 @@ import org.junit.Test; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import static org.junit.Assert.assertEquals; @@ -29,7 +28,7 @@ public void labelShouldBeOriginalNotNormalized() { String rendered = HtmlRenderer.builder().build().render(parser.parse(input)); // Lookup should pass original label to context - assertEquals(Collections.singletonList("FooBarBaz"), inlineParserFactory.lookups); + assertEquals(List.of("FooBarBaz"), inlineParserFactory.lookups); // Context should normalize label for finding reference assertEquals("

      link with special label

      \n", rendered); diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 9a91aa40a..a8a36f707 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -11,7 +11,10 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -75,7 +78,7 @@ public void enabledBlockTypes() { @Test(expected = IllegalArgumentException.class) public void enabledBlockTypesThrowsWhenGivenUnknownClass() { // BulletList can't be enabled separately at the moment, only all ListBlock types - Parser.builder().enabledBlockTypes(new HashSet<>(Arrays.asList(Heading.class, BulletList.class))).build(); + Parser.builder().enabledBlockTypes(Set.of(Heading.class, BulletList.class)).build(); } @Test diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index 59241e49d..8eb6260d0 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -6,7 +6,6 @@ import org.junit.Test; import java.util.ArrayDeque; -import java.util.Arrays; import java.util.Deque; import java.util.List; @@ -91,7 +90,7 @@ public void fencedCodeBlock() { Node document = PARSER.parse("```\nfoo\n```\nbar\n"); Paragraph paragraph = (Paragraph) document.getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(3, 0, 3)), paragraph.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(3, 0, 3)), paragraph.getSourceSpans()); } @Test @@ -134,7 +133,7 @@ public void listBlock() { Node document = PARSER.parse("* foo\n * bar\n"); ListBlock listBlock = (ListBlock) document.getFirstChild().getFirstChild().getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(1, 2, 5)), listBlock.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(1, 2, 5)), listBlock.getSourceSpans()); } @Test @@ -159,10 +158,10 @@ public void linkReferenceDefinition() { Node document = PARSER.parse("[foo]: /url\ntext\n"); LinkReferenceDefinition linkReferenceDefinition = (LinkReferenceDefinition) document.getFirstChild(); - assertEquals(Arrays.asList(SourceSpan.of(0, 0, 11)), linkReferenceDefinition.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 11)), linkReferenceDefinition.getSourceSpans()); Paragraph paragraph = (Paragraph) document.getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(1, 0, 4)), paragraph.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(1, 0, 4)), paragraph.getSourceSpans()); } @Test @@ -199,10 +198,10 @@ public void linkReferenceDefinitionHeading() { Node document = PARSER.parse("[foo]: /url\nHeading\n===\n"); LinkReferenceDefinition linkReferenceDefinition = (LinkReferenceDefinition) document.getFirstChild(); - assertEquals(Arrays.asList(SourceSpan.of(0, 0, 11)), linkReferenceDefinition.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 11)), linkReferenceDefinition.getSourceSpans()); Heading heading = (Heading) document.getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(1, 0, 7), SourceSpan.of(2, 0, 3)), heading.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(1, 0, 7), SourceSpan.of(2, 0, 3)), heading.getSourceSpans()); } @Test @@ -296,7 +295,7 @@ public void inlineEmphasis() { Node document = INLINES_PARSER.parse("*hey**"); Node lastText = document.getFirstChild().getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(0, 5, 1)), lastText.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 5, 1)), lastText.getSourceSpans()); } @Test @@ -322,7 +321,7 @@ private static void assertInlineSpans(String input, Class nodeCl private static void assertSpans(Node rootNode, Class nodeClass, SourceSpan... expectedSourceSpans) { Node node = findNode(rootNode, nodeClass); - assertEquals(Arrays.asList(expectedSourceSpans), node.getSourceSpans()); + assertEquals(List.of(expectedSourceSpans), node.getSourceSpans()); } private static Node findNode(Node rootNode, Class nodeClass) { diff --git a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java index 99da7aa25..e7bb080a8 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java @@ -11,7 +11,6 @@ import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; -import java.util.Collections; import java.util.List; @State(Scope.Benchmark) @@ -37,7 +36,7 @@ public static void main(String[] args) throws Exception { @Benchmark public long parseWholeSpec() { - return parse(Collections.singletonList(SPEC)); + return parse(List.of(SPEC)); } @Benchmark @@ -47,7 +46,7 @@ public long parseExamples() { @Benchmark public long parseAndRenderWholeSpec() { - return parseAndRender(Collections.singletonList(SPEC)); + return parseAndRender(List.of(SPEC)); } @Benchmark diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 08235965a..1ffb7b64a 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -12,7 +12,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.util.Collections; import java.util.Map; import java.util.Set; @@ -126,7 +125,7 @@ class IndentedCodeBlockNodeRenderer implements NodeRenderer { @Override public Set> getNodeTypes() { // Return the node types we want to use this renderer for. - return Collections.>singleton(IndentedCodeBlock.class); + return Set.of(IndentedCodeBlock.class); } @Override From cf6e03a5abac5d55f64a63bdb21c37e1f4775fba Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 26 Apr 2024 23:05:53 +1000 Subject: [PATCH 092/345] Use StandardCharsets instead of Charset.forName --- .../src/main/java/org/commonmark/testutil/TestResources.java | 4 ++-- .../java/org/commonmark/testutil/example/ExampleReader.java | 4 ++-- .../src/main/java/org/commonmark/internal/util/Escaping.java | 3 ++- commonmark/src/test/java/org/commonmark/test/ParserTest.java | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java index 3012f2714..5af649a86 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.List; public class TestResources { @@ -26,7 +26,7 @@ public static List getRegressions() { public static String readAsString(URL url) { StringBuilder sb = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), Charset.forName("UTF-8")))) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { sb.append(line); diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java index e93d2e07c..6f5dd6276 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java @@ -2,7 +2,7 @@ import java.io.*; import java.net.URL; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; @@ -66,7 +66,7 @@ private List read() throws IOException { resetContents(); try (BufferedReader reader = new BufferedReader( - new InputStreamReader(inputStream, Charset.forName("UTF-8")))) { + new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { processLine(line); diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java index ade64d933..27f59ebf7 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java @@ -1,6 +1,7 @@ package org.commonmark.internal.util; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -49,7 +50,7 @@ public void replace(String input, StringBuilder sb) { sb.append(input, 1, input.length()); } } else { - byte[] bytes = input.getBytes(Charset.forName("UTF-8")); + byte[] bytes = input.getBytes(StandardCharsets.UTF_8); for (byte b : bytes) { sb.append('%'); sb.append(HEX_DIGITS[(b >> 4) & 0xF]); diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index a8a36f707..f56cd560c 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -10,7 +10,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -32,7 +32,7 @@ public void ioReaderTest() throws IOException { InputStream input1 = TestResources.getSpec().openStream(); Node document1; - try (InputStreamReader reader = new InputStreamReader(input1, Charset.forName("UTF-8"))) { + try (InputStreamReader reader = new InputStreamReader(input1, StandardCharsets.UTF_8)) { document1 = parser.parseReader(reader); } From 0a969a0da2b3eebb2702cfa19092293f30f92dcb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 26 Apr 2024 23:11:22 +1000 Subject: [PATCH 093/345] Use Objects.requireNonNull --- .../java/org/commonmark/parser/Parser.java | 29 +++++-------------- .../org/commonmark/parser/SourceLine.java | 7 ++--- .../renderer/html/HtmlRenderer.java | 25 ++++------------ .../commonmark/renderer/html/HtmlWriter.java | 5 ++-- 4 files changed, 18 insertions(+), 48 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 129110b99..917534181 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -67,9 +67,7 @@ public static Builder builder() { * @return the root node */ public Node parse(String input) { - if (input == null) { - throw new NullPointerException("input must not be null"); - } + Objects.requireNonNull(input, "input must not be null"); DocumentParser documentParser = createDocumentParser(); Node document = documentParser.parse(input); return postProcess(document); @@ -94,10 +92,7 @@ public Node parse(String input) { * @throws IOException when reading throws an exception */ public Node parseReader(Reader input) throws IOException { - if (input == null) { - throw new NullPointerException("input must not be null"); - } - + Objects.requireNonNull(input, "input must not be null"); DocumentParser documentParser = createDocumentParser(); Node document = documentParser.parse(input); return postProcess(document); @@ -138,9 +133,7 @@ public Parser build() { * @return {@code this} */ public Builder extensions(Iterable extensions) { - if (extensions == null) { - throw new NullPointerException("extensions must not be null"); - } + Objects.requireNonNull(extensions, "extensions must not be null"); for (Extension extension : extensions) { if (extension instanceof ParserExtension) { ParserExtension parserExtension = (ParserExtension) extension; @@ -178,9 +171,7 @@ public Builder extensions(Iterable extensions) { * @return {@code this} */ public Builder enabledBlockTypes(Set> enabledBlockTypes) { - if (enabledBlockTypes == null) { - throw new NullPointerException("enabledBlockTypes must not be null"); - } + Objects.requireNonNull(enabledBlockTypes, "enabledBlockTypes must not be null"); DocumentParser.checkEnabledBlockTypes(enabledBlockTypes); this.enabledBlockTypes = enabledBlockTypes; return this; @@ -211,9 +202,7 @@ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { * @return {@code this} */ public Builder customBlockParserFactory(BlockParserFactory blockParserFactory) { - if (blockParserFactory == null) { - throw new NullPointerException("blockParserFactory must not be null"); - } + Objects.requireNonNull(blockParserFactory, "blockParserFactory must not be null"); blockParserFactories.add(blockParserFactory); return this; } @@ -246,17 +235,13 @@ public Builder customInlineContentParserFactory(InlineContentParserFactory inlin * @return {@code this} */ public Builder customDelimiterProcessor(DelimiterProcessor delimiterProcessor) { - if (delimiterProcessor == null) { - throw new NullPointerException("delimiterProcessor must not be null"); - } + Objects.requireNonNull(delimiterProcessor, "delimiterProcessor must not be null"); delimiterProcessors.add(delimiterProcessor); return this; } public Builder postProcessor(PostProcessor postProcessor) { - if (postProcessor == null) { - throw new NullPointerException("postProcessor must not be null"); - } + Objects.requireNonNull(postProcessor, "postProcessor must not be null"); postProcessors.add(postProcessor); return this; } diff --git a/commonmark/src/main/java/org/commonmark/parser/SourceLine.java b/commonmark/src/main/java/org/commonmark/parser/SourceLine.java index 63caceb9e..d73b14ce2 100644 --- a/commonmark/src/main/java/org/commonmark/parser/SourceLine.java +++ b/commonmark/src/main/java/org/commonmark/parser/SourceLine.java @@ -2,6 +2,8 @@ import org.commonmark.node.SourceSpan; +import java.util.Objects; + /** * A line or part of a line from the input source. * @@ -17,10 +19,7 @@ public static SourceLine of(CharSequence content, SourceSpan sourceSpan) { } private SourceLine(CharSequence content, SourceSpan sourceSpan) { - if (content == null) { - throw new NullPointerException("content must not be null"); - } - this.content = content; + this.content = Objects.requireNonNull(content, "content must not be null"); this.sourceSpan = sourceSpan; } diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java index 19f53594f..06ad01634 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java @@ -7,10 +7,7 @@ import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.Renderer; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Renders a tree of nodes to HTML. @@ -61,18 +58,14 @@ public static Builder builder() { @Override public void render(Node node, Appendable output) { - if (node == null) { - throw new NullPointerException("node must not be null"); - } + Objects.requireNonNull(node, "node must not be null"); RendererContext context = new RendererContext(new HtmlWriter(output)); context.render(node); } @Override public String render(Node node) { - if (node == null) { - throw new NullPointerException("node must not be null"); - } + Objects.requireNonNull(node, "node must not be null"); StringBuilder sb = new StringBuilder(); render(node, sb); return sb.toString(); @@ -178,9 +171,7 @@ public Builder percentEncodeUrls(boolean percentEncodeUrls) { * @return {@code this} */ public Builder attributeProviderFactory(AttributeProviderFactory attributeProviderFactory) { - if (attributeProviderFactory == null) { - throw new NullPointerException("attributeProviderFactory must not be null"); - } + Objects.requireNonNull(attributeProviderFactory, "attributeProviderFactory must not be null"); this.attributeProviderFactories.add(attributeProviderFactory); return this; } @@ -196,9 +187,7 @@ public Builder attributeProviderFactory(AttributeProviderFactory attributeProvid * @return {@code this} */ public Builder nodeRendererFactory(HtmlNodeRendererFactory nodeRendererFactory) { - if (nodeRendererFactory == null) { - throw new NullPointerException("nodeRendererFactory must not be null"); - } + Objects.requireNonNull(nodeRendererFactory, "nodeRendererFactory must not be null"); this.nodeRendererFactories.add(nodeRendererFactory); return this; } @@ -208,9 +197,7 @@ public Builder nodeRendererFactory(HtmlNodeRendererFactory nodeRendererFactory) * @return {@code this} */ public Builder extensions(Iterable extensions) { - if (extensions == null) { - throw new NullPointerException("extensions must not be null"); - } + Objects.requireNonNull(extensions, "extensions must not be null"); for (Extension extension : extensions) { if (extension instanceof HtmlRendererExtension) { HtmlRendererExtension htmlRendererExtension = (HtmlRendererExtension) extension; diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java index 2b4b2896d..7df185e48 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.Map; +import java.util.Objects; public class HtmlWriter { @@ -13,9 +14,7 @@ public class HtmlWriter { private char lastChar = 0; public HtmlWriter(Appendable out) { - if (out == null) { - throw new NullPointerException("out must not be null"); - } + Objects.requireNonNull(out, "out must not be null"); this.buffer = out; } From 8c1abde9aadfc95573cc8bf16e8a6dc845a6eeaa Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 27 Apr 2024 18:43:24 +1000 Subject: [PATCH 094/345] Bump jmh dependency --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d4e49cd80..3e17f85ae 100644 --- a/pom.xml +++ b/pom.xml @@ -168,12 +168,12 @@ org.openjdk.jmh jmh-core - 1.17.5 + 1.37 org.openjdk.jmh jmh-generator-annprocess - 1.17.5 + 1.37 From 80d7929cb8560dc8c77de027290d43d31054468e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 27 Apr 2024 18:44:01 +1000 Subject: [PATCH 095/345] Simplify prepareLine It's called once every line, and we only ever use it with a String, so might as well take advantage. indexOf should be faster than manually iterating. --- .../commonmark/internal/DocumentParser.java | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index e8c0f837b..dd59b1a36 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -1,9 +1,9 @@ package org.commonmark.internal; -import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.*; +import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.block.*; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.text.Characters; @@ -126,7 +126,7 @@ public Document parse(String input) { lineStart = lineBreak + 1; } } - if (input.length() > 0 && (lineStart == 0 || lineStart < input.length())) { + if (!input.isEmpty() && (lineStart == 0 || lineStart < input.length())) { String line = input.substring(lineStart); parseLine(line); } @@ -189,7 +189,7 @@ public BlockParser getActiveBlockParser() { * Analyze a line of text and update the document appropriately. We parse markdown text by calling this on each * line of input, then finalizing the document. */ - private void parseLine(CharSequence ln) { + private void parseLine(String ln) { setLine(ln); // For each containing block, try to parse the associated line start. @@ -314,13 +314,13 @@ private void parseLine(CharSequence ln) { } } - private void setLine(CharSequence ln) { + private void setLine(String ln) { lineIndex++; index = 0; column = 0; columnIsInTab = false; - CharSequence lineContent = prepareLine(ln); + String lineContent = prepareLine(ln); SourceSpan sourceSpan = null; if (includeSourceSpans != IncludeSourceSpans.NONE) { sourceSpan = SourceSpan.of(lineIndex, 0, lineContent.length()); @@ -550,29 +550,11 @@ private void closeBlockParsers(int count) { /** * Prepares the input line replacing {@code \0} */ - private static CharSequence prepareLine(CharSequence line) { - // Avoid building a new string in the majority of cases (no \0) - StringBuilder sb = null; - int length = line.length(); - for (int i = 0; i < length; i++) { - char c = line.charAt(i); - if (c == '\0') { - if (sb == null) { - sb = new StringBuilder(length); - sb.append(line, 0, i); - } - sb.append('\uFFFD'); - } else { - if (sb != null) { - sb.append(c); - } - } - } - - if (sb != null) { - return sb.toString(); - } else { + private static String prepareLine(String line) { + if (line.indexOf('\0') == -1) { return line; + } else { + return line.replace('\0', '\uFFFD'); } } From 4edcb3b5fe104d0a10af195dba7f897aa927e6a1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 27 Apr 2024 19:10:09 +1000 Subject: [PATCH 096/345] Bump codecov action, use token --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fa93f210..2b47fab97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,10 @@ jobs: run: mvn -B -Pcoverage clean test jacoco:report-aggregate - name: Publish coverage - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} android-compatibility: runs-on: ubuntu-latest From 72d8d70b9336d9b61688cdb5e1ee27a4a226078e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 27 Apr 2024 19:12:25 +1000 Subject: [PATCH 097/345] Bump checkout action --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b47fab97..b76e50529 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: java: [11, 17, 21] steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v2 @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v2 @@ -48,7 +48,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v2 From 0c1378dbd329892ea6d857ba323025628eb8e832 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 27 Apr 2024 21:22:33 +1000 Subject: [PATCH 098/345] Only run coverage on push, not on pull_request No need to run it twice, running it on the branch is enough, no need to measure coverage on the merge commit too. --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b76e50529..f4c632dd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,7 @@ jobs: coverage: runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' }} steps: - name: Checkout sources uses: actions/checkout@v4 From fa8a0a8e4bfaca23546f7b2051b9c84a2639ca03 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 27 Apr 2024 21:29:03 +1000 Subject: [PATCH 099/345] Bump actions/setup-java from v2 to v4 --- .github/workflows/ci.yml | 6 +++--- .github/workflows/release.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4c632dd5..b49fb8164 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'zulu' @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: 11 distribution: 'zulu' @@ -52,7 +52,7 @@ jobs: uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: 11 distribution: 'zulu' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ecfa6d49..4727b103d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,10 +14,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Maven Central repository - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: 11 distribution: 'zulu' From 682bdb09932a4846c389e9db43eba1b857e219cd Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 27 Apr 2024 21:42:54 +1000 Subject: [PATCH 100/345] pom: Bump versions, update metadata --- commonmark/pom.xml | 2 +- pom.xml | 21 +++++++++------------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/commonmark/pom.xml b/commonmark/pom.xml index bcb5b3bf0..d47260296 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -38,7 +38,7 @@ org.codehaus.mojo exec-maven-plugin - 1.5.0 + 3.2.0 java test diff --git a/pom.xml b/pom.xml index 3e17f85ae..465c3b8e4 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.12.1 + 3.13.0 11 @@ -46,17 +46,17 @@ org.apache.maven.plugins maven-jar-plugin - 3.3.0 + 3.4.1 org.apache.maven.plugins maven-install-plugin - 3.0.1 + 3.1.1 org.apache.maven.plugins maven-javadoc-plugin - 3.5.0 + 3.6.3 *.internal,*.internal.* @@ -73,7 +73,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + 3.2.5 @@ -94,7 +94,7 @@ org.apache.maven.plugins maven-release-plugin - 3.0.0-M7 + 3.0.1 true false @@ -186,7 +186,7 @@ org.apache.maven.plugins maven-source-plugin - 3.2.1 + 3.3.1 attach-sources @@ -211,7 +211,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.0.1 + 3.2.4 sign-artifacts @@ -238,7 +238,7 @@ org.jacoco jacoco-maven-plugin - 0.8.8 + 0.8.12 @@ -271,9 +271,6 @@ Robin Stocker - rstocker@atlassian.com - Atlassian - https://www.atlassian.com/ From f14989bcdda958ae7249ffb3effe0883159ee2a3 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 28 Apr 2024 11:57:20 +1000 Subject: [PATCH 101/345] Remove Eclipse .settings They were only in a subset of the modules and probably out of date anyway. Maven import should just work nowadays. --- .../.settings/org.eclipse.core.runtime.prefs | 2 - .../.settings/org.eclipse.jdt.core.prefs | 290 ------------------ .../.settings/org.eclipse.core.runtime.prefs | 2 - .../.settings/org.eclipse.jdt.core.prefs | 290 ------------------ .../.settings/org.eclipse.core.runtime.prefs | 2 - .../.settings/org.eclipse.jdt.core.prefs | 290 ------------------ .../.settings/org.eclipse.core.runtime.prefs | 2 - .../.settings/org.eclipse.jdt.core.prefs | 290 ------------------ .../.settings/org.eclipse.core.runtime.prefs | 2 - .../.settings/org.eclipse.jdt.core.prefs | 290 ------------------ 10 files changed, 1460 deletions(-) delete mode 100644 commonmark-ext-autolink/.settings/org.eclipse.core.runtime.prefs delete mode 100644 commonmark-ext-autolink/.settings/org.eclipse.jdt.core.prefs delete mode 100644 commonmark-ext-gfm-strikethrough/.settings/org.eclipse.core.runtime.prefs delete mode 100644 commonmark-ext-gfm-strikethrough/.settings/org.eclipse.jdt.core.prefs delete mode 100644 commonmark-ext-gfm-tables/.settings/org.eclipse.core.runtime.prefs delete mode 100644 commonmark-ext-gfm-tables/.settings/org.eclipse.jdt.core.prefs delete mode 100644 commonmark-integration-test/.settings/org.eclipse.core.runtime.prefs delete mode 100644 commonmark-integration-test/.settings/org.eclipse.jdt.core.prefs delete mode 100644 commonmark/.settings/org.eclipse.core.runtime.prefs delete mode 100644 commonmark/.settings/org.eclipse.jdt.core.prefs diff --git a/commonmark-ext-autolink/.settings/org.eclipse.core.runtime.prefs b/commonmark-ext-autolink/.settings/org.eclipse.core.runtime.prefs deleted file mode 100644 index 5a0ad22d2..000000000 --- a/commonmark-ext-autolink/.settings/org.eclipse.core.runtime.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -line.separator=\n diff --git a/commonmark-ext-autolink/.settings/org.eclipse.jdt.core.prefs b/commonmark-ext-autolink/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 3c0d27c8f..000000000 --- a/commonmark-ext-autolink/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,290 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.compliance=1.7 -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.7 -org.eclipse.jdt.core.formatter.align_type_members_on_columns=false -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_assignment=0 -org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 -org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 -org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 -org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 -org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 -org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 -org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 -org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_after_package=1 -org.eclipse.jdt.core.formatter.blank_lines_before_field=0 -org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 -org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 -org.eclipse.jdt.core.formatter.blank_lines_before_method=1 -org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 -org.eclipse.jdt.core.formatter.blank_lines_before_package=0 -org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 -org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 -org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false -org.eclipse.jdt.core.formatter.comment.format_block_comments=true -org.eclipse.jdt.core.formatter.comment.format_header=false -org.eclipse.jdt.core.formatter.comment.format_html=true -org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true -org.eclipse.jdt.core.formatter.comment.format_line_comments=true -org.eclipse.jdt.core.formatter.comment.format_source_code=true -org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true -org.eclipse.jdt.core.formatter.comment.indent_root_tags=true -org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert -org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert -org.eclipse.jdt.core.formatter.comment.line_length=120 -org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true -org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true -org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false -org.eclipse.jdt.core.formatter.compact_else_if=true -org.eclipse.jdt.core.formatter.continuation_indentation=2 -org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 -org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off -org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on -org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false -org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true -org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_empty_lines=false -org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true -org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true -org.eclipse.jdt.core.formatter.indentation.size=4 -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert -org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert -org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert -org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.join_lines_in_comments=true -org.eclipse.jdt.core.formatter.join_wrapped_lines=false -org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false -org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false -org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false -org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false -org.eclipse.jdt.core.formatter.lineSplit=120 -org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false -org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false -org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 -org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 -org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true -org.eclipse.jdt.core.formatter.tabulation.char=space -org.eclipse.jdt.core.formatter.tabulation.size=4 -org.eclipse.jdt.core.formatter.use_on_off_tags=false -org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false -org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true -org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true -org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true -org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter diff --git a/commonmark-ext-gfm-strikethrough/.settings/org.eclipse.core.runtime.prefs b/commonmark-ext-gfm-strikethrough/.settings/org.eclipse.core.runtime.prefs deleted file mode 100644 index 5a0ad22d2..000000000 --- a/commonmark-ext-gfm-strikethrough/.settings/org.eclipse.core.runtime.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -line.separator=\n diff --git a/commonmark-ext-gfm-strikethrough/.settings/org.eclipse.jdt.core.prefs b/commonmark-ext-gfm-strikethrough/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 3c0d27c8f..000000000 --- a/commonmark-ext-gfm-strikethrough/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,290 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.compliance=1.7 -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.7 -org.eclipse.jdt.core.formatter.align_type_members_on_columns=false -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_assignment=0 -org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 -org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 -org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 -org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 -org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 -org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 -org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 -org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_after_package=1 -org.eclipse.jdt.core.formatter.blank_lines_before_field=0 -org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 -org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 -org.eclipse.jdt.core.formatter.blank_lines_before_method=1 -org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 -org.eclipse.jdt.core.formatter.blank_lines_before_package=0 -org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 -org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 -org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false -org.eclipse.jdt.core.formatter.comment.format_block_comments=true -org.eclipse.jdt.core.formatter.comment.format_header=false -org.eclipse.jdt.core.formatter.comment.format_html=true -org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true -org.eclipse.jdt.core.formatter.comment.format_line_comments=true -org.eclipse.jdt.core.formatter.comment.format_source_code=true -org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true -org.eclipse.jdt.core.formatter.comment.indent_root_tags=true -org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert -org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert -org.eclipse.jdt.core.formatter.comment.line_length=120 -org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true -org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true -org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false -org.eclipse.jdt.core.formatter.compact_else_if=true -org.eclipse.jdt.core.formatter.continuation_indentation=2 -org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 -org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off -org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on -org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false -org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true -org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_empty_lines=false -org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true -org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true -org.eclipse.jdt.core.formatter.indentation.size=4 -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert -org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert -org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert -org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.join_lines_in_comments=true -org.eclipse.jdt.core.formatter.join_wrapped_lines=false -org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false -org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false -org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false -org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false -org.eclipse.jdt.core.formatter.lineSplit=120 -org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false -org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false -org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 -org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 -org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true -org.eclipse.jdt.core.formatter.tabulation.char=space -org.eclipse.jdt.core.formatter.tabulation.size=4 -org.eclipse.jdt.core.formatter.use_on_off_tags=false -org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false -org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true -org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true -org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true -org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter diff --git a/commonmark-ext-gfm-tables/.settings/org.eclipse.core.runtime.prefs b/commonmark-ext-gfm-tables/.settings/org.eclipse.core.runtime.prefs deleted file mode 100644 index 5a0ad22d2..000000000 --- a/commonmark-ext-gfm-tables/.settings/org.eclipse.core.runtime.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -line.separator=\n diff --git a/commonmark-ext-gfm-tables/.settings/org.eclipse.jdt.core.prefs b/commonmark-ext-gfm-tables/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 3c0d27c8f..000000000 --- a/commonmark-ext-gfm-tables/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,290 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.compliance=1.7 -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.7 -org.eclipse.jdt.core.formatter.align_type_members_on_columns=false -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_assignment=0 -org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 -org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 -org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 -org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 -org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 -org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 -org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 -org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_after_package=1 -org.eclipse.jdt.core.formatter.blank_lines_before_field=0 -org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 -org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 -org.eclipse.jdt.core.formatter.blank_lines_before_method=1 -org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 -org.eclipse.jdt.core.formatter.blank_lines_before_package=0 -org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 -org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 -org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false -org.eclipse.jdt.core.formatter.comment.format_block_comments=true -org.eclipse.jdt.core.formatter.comment.format_header=false -org.eclipse.jdt.core.formatter.comment.format_html=true -org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true -org.eclipse.jdt.core.formatter.comment.format_line_comments=true -org.eclipse.jdt.core.formatter.comment.format_source_code=true -org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true -org.eclipse.jdt.core.formatter.comment.indent_root_tags=true -org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert -org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert -org.eclipse.jdt.core.formatter.comment.line_length=120 -org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true -org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true -org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false -org.eclipse.jdt.core.formatter.compact_else_if=true -org.eclipse.jdt.core.formatter.continuation_indentation=2 -org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 -org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off -org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on -org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false -org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true -org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_empty_lines=false -org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true -org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true -org.eclipse.jdt.core.formatter.indentation.size=4 -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert -org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert -org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert -org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.join_lines_in_comments=true -org.eclipse.jdt.core.formatter.join_wrapped_lines=false -org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false -org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false -org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false -org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false -org.eclipse.jdt.core.formatter.lineSplit=120 -org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false -org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false -org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 -org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 -org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true -org.eclipse.jdt.core.formatter.tabulation.char=space -org.eclipse.jdt.core.formatter.tabulation.size=4 -org.eclipse.jdt.core.formatter.use_on_off_tags=false -org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false -org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true -org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true -org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true -org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter diff --git a/commonmark-integration-test/.settings/org.eclipse.core.runtime.prefs b/commonmark-integration-test/.settings/org.eclipse.core.runtime.prefs deleted file mode 100644 index 5a0ad22d2..000000000 --- a/commonmark-integration-test/.settings/org.eclipse.core.runtime.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -line.separator=\n diff --git a/commonmark-integration-test/.settings/org.eclipse.jdt.core.prefs b/commonmark-integration-test/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 3c0d27c8f..000000000 --- a/commonmark-integration-test/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,290 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.compliance=1.7 -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.7 -org.eclipse.jdt.core.formatter.align_type_members_on_columns=false -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_assignment=0 -org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 -org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 -org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 -org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 -org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 -org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 -org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 -org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_after_package=1 -org.eclipse.jdt.core.formatter.blank_lines_before_field=0 -org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 -org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 -org.eclipse.jdt.core.formatter.blank_lines_before_method=1 -org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 -org.eclipse.jdt.core.formatter.blank_lines_before_package=0 -org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 -org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 -org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false -org.eclipse.jdt.core.formatter.comment.format_block_comments=true -org.eclipse.jdt.core.formatter.comment.format_header=false -org.eclipse.jdt.core.formatter.comment.format_html=true -org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true -org.eclipse.jdt.core.formatter.comment.format_line_comments=true -org.eclipse.jdt.core.formatter.comment.format_source_code=true -org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true -org.eclipse.jdt.core.formatter.comment.indent_root_tags=true -org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert -org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert -org.eclipse.jdt.core.formatter.comment.line_length=120 -org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true -org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true -org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false -org.eclipse.jdt.core.formatter.compact_else_if=true -org.eclipse.jdt.core.formatter.continuation_indentation=2 -org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 -org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off -org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on -org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false -org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true -org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_empty_lines=false -org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true -org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true -org.eclipse.jdt.core.formatter.indentation.size=4 -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert -org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert -org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert -org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.join_lines_in_comments=true -org.eclipse.jdt.core.formatter.join_wrapped_lines=false -org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false -org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false -org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false -org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false -org.eclipse.jdt.core.formatter.lineSplit=120 -org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false -org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false -org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 -org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 -org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true -org.eclipse.jdt.core.formatter.tabulation.char=space -org.eclipse.jdt.core.formatter.tabulation.size=4 -org.eclipse.jdt.core.formatter.use_on_off_tags=false -org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false -org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true -org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true -org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true -org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter diff --git a/commonmark/.settings/org.eclipse.core.runtime.prefs b/commonmark/.settings/org.eclipse.core.runtime.prefs deleted file mode 100644 index 5a0ad22d2..000000000 --- a/commonmark/.settings/org.eclipse.core.runtime.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -line.separator=\n diff --git a/commonmark/.settings/org.eclipse.jdt.core.prefs b/commonmark/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 3c0d27c8f..000000000 --- a/commonmark/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,290 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.compliance=1.7 -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.7 -org.eclipse.jdt.core.formatter.align_type_members_on_columns=false -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_assignment=0 -org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 -org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 -org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 -org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 -org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 -org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 -org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 -org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_after_package=1 -org.eclipse.jdt.core.formatter.blank_lines_before_field=0 -org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 -org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 -org.eclipse.jdt.core.formatter.blank_lines_before_method=1 -org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 -org.eclipse.jdt.core.formatter.blank_lines_before_package=0 -org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 -org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 -org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false -org.eclipse.jdt.core.formatter.comment.format_block_comments=true -org.eclipse.jdt.core.formatter.comment.format_header=false -org.eclipse.jdt.core.formatter.comment.format_html=true -org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true -org.eclipse.jdt.core.formatter.comment.format_line_comments=true -org.eclipse.jdt.core.formatter.comment.format_source_code=true -org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true -org.eclipse.jdt.core.formatter.comment.indent_root_tags=true -org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert -org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert -org.eclipse.jdt.core.formatter.comment.line_length=120 -org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true -org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true -org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false -org.eclipse.jdt.core.formatter.compact_else_if=true -org.eclipse.jdt.core.formatter.continuation_indentation=2 -org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 -org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off -org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on -org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false -org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true -org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_empty_lines=false -org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true -org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true -org.eclipse.jdt.core.formatter.indentation.size=4 -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert -org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert -org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert -org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.join_lines_in_comments=true -org.eclipse.jdt.core.formatter.join_wrapped_lines=false -org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false -org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false -org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false -org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false -org.eclipse.jdt.core.formatter.lineSplit=120 -org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false -org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false -org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 -org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 -org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true -org.eclipse.jdt.core.formatter.tabulation.char=space -org.eclipse.jdt.core.formatter.tabulation.size=4 -org.eclipse.jdt.core.formatter.use_on_off_tags=false -org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false -org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true -org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true -org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true -org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter From 3d5c7309ebbf39d845b349c2446d5b06ed9deb92 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 6 Apr 2024 18:08:08 +0700 Subject: [PATCH 102/345] WIP footnotes: Block parsing --- .../org/commonmark/test/FootnotesTest.java | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 commonmark/src/test/java/org/commonmark/test/FootnotesTest.java diff --git a/commonmark/src/test/java/org/commonmark/test/FootnotesTest.java b/commonmark/src/test/java/org/commonmark/test/FootnotesTest.java new file mode 100644 index 000000000..a2afc8deb --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/FootnotesTest.java @@ -0,0 +1,168 @@ +package org.commonmark.test; + +import org.commonmark.Extension; +import org.commonmark.node.*; +import org.commonmark.parser.Parser; +import org.commonmark.parser.block.*; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class FootnotesTest { + + public static class FootnoteDefinition extends CustomBlock { + + private String label; + + public FootnoteDefinition(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + } + + public static class FootnotesExtension implements Parser.ParserExtension { + + public static Extension create() { + return new FootnotesExtension(); + } + + @Override + public void extend(Parser.Builder parserBuilder) { + parserBuilder.customBlockParserFactory(new FootnoteBlockParser.Factory()); + } + } + + public static class FootnoteBlockParser extends AbstractBlockParser { + + private final FootnoteDefinition block; + + public FootnoteBlockParser(String label) { + block = new FootnoteDefinition(label); + } + + @Override + public Block getBlock() { + return block; + } + + @Override + public boolean canHaveLazyContinuationLines() { + return true; + } + + @Override + public BlockContinue tryContinue(ParserState parserState) { + // We're not continuing to give other block parsers a chance to interrupt this definition. + // But if no other block parser applied (including another FootnotesBlockParser), we will + // accept the line via lazy continuation. + return BlockContinue.none(); + } + + public static class Factory implements BlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + var content = state.getLine().getContent(); + // TODO: Can it be indented? Maybe less than code block indent. + var index = state.getNextNonSpaceIndex(); + if (content.charAt(index) != '[' || index + 1 >= content.length()) { + return BlockStart.none(); + } + index++; + if (content.charAt(index) != '^' || index + 1 >= content.length()) { + return BlockStart.none(); + } + // Now at first label character (if any) + index++; + + for (int i = index; i < content.length(); i++) { + var c = content.charAt(i); + if (c == ']') { + if (i > index) { + var label = content.subSequence(index, i).toString(); + return BlockStart.of(new FootnoteBlockParser(label)); + } else { + return BlockStart.none(); + } + } + // TODO: Check what GitHub actually does here, e.g. tabs, control characters, other Unicode whitespace + if (Character.isWhitespace(c)) { + return BlockStart.none(); + } + } + + return BlockStart.none(); + } + } + } + + private final Parser PARSER = Parser.builder().extensions(List.of(FootnotesExtension.create())).build(); + + @Test + public void testBlockStart() { + for (var s : List.of("1", "a")) { + var doc = PARSER.parse("[^" + s + "]: footnote\n"); + var def = Nodes.find(doc, FootnoteDefinition.class); + // TODO: Should label be "^1" instead? + assertEquals(s, def.getLabel()); + } + + for (var s : List.of("", " ", "a b")) { + var doc = PARSER.parse("[^" + s + "]: footnote\n"); + assertNull(Nodes.tryFind(doc, FootnoteDefinition.class)); + } + + // TODO: Test what characters are allowed for the label, e.g. + // [^], [^ ], [^^], [^[], [^*], [^\], [^\a], [^🙂], tab?, [^&], [^&] + } + + @Test + public void testBlockStartInterrupts() { + var doc = PARSER.parse("test\n[^1]: footnote\n"); + var paragraph = Nodes.find(doc, Paragraph.class); + var def = Nodes.find(doc, FootnoteDefinition.class); + assertEquals("test", ((Text) paragraph.getLastChild()).getLiteral()); + assertEquals("1", def.getLabel()); + } + + @Test + public void testMultiple() { + var doc = PARSER.parse("[^1]: foo\n[^2]: bar\n"); + var def1 = (FootnoteDefinition) doc.getFirstChild(); + var def2 = (FootnoteDefinition) doc.getLastChild(); + assertEquals("1", def1.getLabel()); + assertEquals("2", def2.getLabel()); + } + + @Test + public void testBlockStartAfterLinkReferenceDefinition() { + var doc = PARSER.parse("[foo]: /url\n[^1]: footnote\n"); + var linkReferenceDef = Nodes.find(doc, LinkReferenceDefinition.class); + var footnotesDef = Nodes.find(doc, FootnoteDefinition.class); + assertEquals("foo", linkReferenceDef.getLabel()); + assertEquals("1", footnotesDef.getLabel()); + } + + @Test + public void testBlockContinue() { + var doc = PARSER.parse("[^1]: footnote\nstill\n"); + var def = Nodes.find(doc, FootnoteDefinition.class); + assertEquals("1", def.getLabel()); + assertNull(Nodes.tryFind(doc, Paragraph.class)); + } + + @Test + public void testFootnotesDefinitionInterruptedByOthers() { + var doc = PARSER.parse("[^1]: footnote\n# Heading\n"); + var def = Nodes.find(doc, FootnoteDefinition.class); + var heading = Nodes.find(doc, Heading.class); + assertEquals("1", def.getLabel()); + assertEquals("Heading", ((Text) heading.getFirstChild()).getLiteral()); + } +} From 1f6e729be791317f16371eb7cf428691599fbc47 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 28 Apr 2024 12:26:53 +1000 Subject: [PATCH 103/345] Move code to new ext-footnotes module with extension --- commonmark-ext-footnotes/pom.xml | 27 +++ .../src/main/java/module-info.java | 5 + .../ext/footnotes/FootnoteDefinition.java | 17 ++ .../ext/footnotes/FootnotesExtension.java | 24 +++ .../internal/FootnoteBlockParser.java | 69 +++++++ .../src/main/javadoc/overview.html | 6 + .../src/main/resources/META-INF/LICENSE.txt | 23 +++ .../ext/footnotes/FootnotesTest.java | 101 +++++++++++ .../org/commonmark/test/FootnotesTest.java | 168 ------------------ .../test/java/org/commonmark/test/Nodes.java | 8 +- pom.xml | 3 +- 11 files changed, 277 insertions(+), 174 deletions(-) create mode 100644 commonmark-ext-footnotes/pom.xml create mode 100644 commonmark-ext-footnotes/src/main/java/module-info.java create mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteDefinition.java create mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java create mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java create mode 100644 commonmark-ext-footnotes/src/main/javadoc/overview.html create mode 100644 commonmark-ext-footnotes/src/main/resources/META-INF/LICENSE.txt create mode 100644 commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java delete mode 100644 commonmark/src/test/java/org/commonmark/test/FootnotesTest.java diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml new file mode 100644 index 000000000..f70b7d262 --- /dev/null +++ b/commonmark-ext-footnotes/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + org.commonmark + commonmark-parent + 0.22.1-SNAPSHOT + + + commonmark-ext-footnotes + commonmark-java extension for footnotes + commonmark-java extension for footnotes using [^1] syntax + + + + org.commonmark + commonmark + + + + org.commonmark + commonmark-test-util + test + + + + diff --git a/commonmark-ext-footnotes/src/main/java/module-info.java b/commonmark-ext-footnotes/src/main/java/module-info.java new file mode 100644 index 000000000..806d65c3e --- /dev/null +++ b/commonmark-ext-footnotes/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module org.commonmark.ext.footnotes { + exports org.commonmark.ext.footnotes; + + requires org.commonmark; +} diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteDefinition.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteDefinition.java new file mode 100644 index 000000000..4adf98462 --- /dev/null +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteDefinition.java @@ -0,0 +1,17 @@ +package org.commonmark.ext.footnotes; + +import org.commonmark.node.CustomBlock; + +public class FootnoteDefinition extends CustomBlock { + + private String label; + + public FootnoteDefinition(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } +} + diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java new file mode 100644 index 000000000..df376aaba --- /dev/null +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java @@ -0,0 +1,24 @@ +package org.commonmark.ext.footnotes; + +import org.commonmark.Extension; +import org.commonmark.ext.footnotes.internal.FootnoteBlockParser; +import org.commonmark.parser.Parser; + +/** + * TODO + */ +// TODO: HTML rendering and Markdown rendering +public class FootnotesExtension implements Parser.ParserExtension { + + private FootnotesExtension() { + } + + public static Extension create() { + return new FootnotesExtension(); + } + + @Override + public void extend(Parser.Builder parserBuilder) { + parserBuilder.customBlockParserFactory(new FootnoteBlockParser.Factory()); + } +} diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java new file mode 100644 index 000000000..99f75cc4c --- /dev/null +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java @@ -0,0 +1,69 @@ +package org.commonmark.ext.footnotes.internal; + +import org.commonmark.ext.footnotes.FootnoteDefinition; +import org.commonmark.node.Block; +import org.commonmark.parser.block.*; + +public class FootnoteBlockParser extends AbstractBlockParser { + + private final FootnoteDefinition block; + + public FootnoteBlockParser(String label) { + block = new FootnoteDefinition(label); + } + + @Override + public Block getBlock() { + return block; + } + + @Override + public boolean canHaveLazyContinuationLines() { + return true; + } + + @Override + public BlockContinue tryContinue(ParserState parserState) { + // We're not continuing to give other block parsers a chance to interrupt this definition. + // But if no other block parser applied (including another FootnotesBlockParser), we will + // accept the line via lazy continuation. + return BlockContinue.none(); + } + + public static class Factory implements BlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + var content = state.getLine().getContent(); + // TODO: Can it be indented? Maybe less than code block indent. + var index = state.getNextNonSpaceIndex(); + if (content.charAt(index) != '[' || index + 1 >= content.length()) { + return BlockStart.none(); + } + index++; + if (content.charAt(index) != '^' || index + 1 >= content.length()) { + return BlockStart.none(); + } + // Now at first label character (if any) + index++; + + for (int i = index; i < content.length(); i++) { + var c = content.charAt(i); + if (c == ']') { + if (i > index) { + var label = content.subSequence(index, i).toString(); + return BlockStart.of(new FootnoteBlockParser(label)); + } else { + return BlockStart.none(); + } + } + // TODO: Check what GitHub actually does here, e.g. tabs, control characters, other Unicode whitespace + if (Character.isWhitespace(c)) { + return BlockStart.none(); + } + } + + return BlockStart.none(); + } + } +} diff --git a/commonmark-ext-footnotes/src/main/javadoc/overview.html b/commonmark-ext-footnotes/src/main/javadoc/overview.html new file mode 100644 index 000000000..4f19d2115 --- /dev/null +++ b/commonmark-ext-footnotes/src/main/javadoc/overview.html @@ -0,0 +1,6 @@ + + +Extension for footnotes using [^1] syntax +

      See {@link org.commonmark.ext.footnotes.FootnotesExtension}

      + + diff --git a/commonmark-ext-footnotes/src/main/resources/META-INF/LICENSE.txt b/commonmark-ext-footnotes/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000..b09e367ce --- /dev/null +++ b/commonmark-ext-footnotes/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,23 @@ +Copyright (c) 2015, Atlassian Pty Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java new file mode 100644 index 000000000..c6fea7277 --- /dev/null +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -0,0 +1,101 @@ +package org.commonmark.ext.footnotes; + +import org.commonmark.Extension; +import org.commonmark.node.*; +import org.commonmark.parser.Parser; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class FootnotesTest { + + private static final Set EXTENSIONS = Set.of(FootnotesExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + + @Test + public void testBlockStart() { + for (var s : List.of("1", "a")) { + var doc = PARSER.parse("[^" + s + "]: footnote\n"); + var def = find(doc, FootnoteDefinition.class); + // TODO: Should label be "^1" instead? + assertEquals(s, def.getLabel()); + } + + for (var s : List.of("", " ", "a b")) { + var doc = PARSER.parse("[^" + s + "]: footnote\n"); + assertNull(tryFind(doc, FootnoteDefinition.class)); + } + + // TODO: Test what characters are allowed for the label, e.g. + // [^], [^ ], [^^], [^[], [^*], [^\], [^\a], [^🙂], tab?, [^&], [^&] + } + + @Test + public void testBlockStartInterrupts() { + var doc = PARSER.parse("test\n[^1]: footnote\n"); + var paragraph = find(doc, Paragraph.class); + var def = find(doc, FootnoteDefinition.class); + assertEquals("test", ((Text) paragraph.getLastChild()).getLiteral()); + assertEquals("1", def.getLabel()); + } + + @Test + public void testMultiple() { + var doc = PARSER.parse("[^1]: foo\n[^2]: bar\n"); + var defs = findAll(doc, FootnoteDefinition.class); + assertEquals("1", defs.get(0).getLabel()); + assertEquals("2", defs.get(1).getLabel()); + } + + @Test + public void testBlockStartAfterLinkReferenceDefinition() { + var doc = PARSER.parse("[foo]: /url\n[^1]: footnote\n"); + var linkReferenceDef = find(doc, LinkReferenceDefinition.class); + var footnotesDef = find(doc, FootnoteDefinition.class); + assertEquals("foo", linkReferenceDef.getLabel()); + assertEquals("1", footnotesDef.getLabel()); + } + + @Test + public void testBlockContinue() { + var doc = PARSER.parse("[^1]: footnote\nstill\n"); + var def = find(doc, FootnoteDefinition.class); + assertEquals("1", def.getLabel()); + assertNull(tryFind(doc, Paragraph.class)); + } + + @Test + public void testFootnotesDefinitionInterruptedByOthers() { + var doc = PARSER.parse("[^1]: footnote\n# Heading\n"); + var def = find(doc, FootnoteDefinition.class); + var heading = find(doc, Heading.class); + assertEquals("1", def.getLabel()); + assertEquals("Heading", ((Text) heading.getFirstChild()).getLiteral()); + } + + private static T find(Node parent, Class nodeClass) { + return Objects.requireNonNull(tryFind(parent, nodeClass), "Could not find a " + nodeClass.getSimpleName() + " node in " + parent); + } + + private static T tryFind(Node parent, Class nodeClass) { + return findAll(parent, nodeClass).stream().findFirst().orElse(null); + } + + private static List findAll(Node parent, Class nodeClass) { + var nodes = new ArrayList(); + for (var node = parent.getFirstChild(); node != null; node = node.getNext()) { + if (nodeClass.isInstance(node)) { + //noinspection unchecked + nodes.add((T) node); + } + nodes.addAll(findAll(node, nodeClass)); + } + return nodes; + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/FootnotesTest.java b/commonmark/src/test/java/org/commonmark/test/FootnotesTest.java deleted file mode 100644 index a2afc8deb..000000000 --- a/commonmark/src/test/java/org/commonmark/test/FootnotesTest.java +++ /dev/null @@ -1,168 +0,0 @@ -package org.commonmark.test; - -import org.commonmark.Extension; -import org.commonmark.node.*; -import org.commonmark.parser.Parser; -import org.commonmark.parser.block.*; -import org.junit.Test; - -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -public class FootnotesTest { - - public static class FootnoteDefinition extends CustomBlock { - - private String label; - - public FootnoteDefinition(String label) { - this.label = label; - } - - public String getLabel() { - return label; - } - } - - public static class FootnotesExtension implements Parser.ParserExtension { - - public static Extension create() { - return new FootnotesExtension(); - } - - @Override - public void extend(Parser.Builder parserBuilder) { - parserBuilder.customBlockParserFactory(new FootnoteBlockParser.Factory()); - } - } - - public static class FootnoteBlockParser extends AbstractBlockParser { - - private final FootnoteDefinition block; - - public FootnoteBlockParser(String label) { - block = new FootnoteDefinition(label); - } - - @Override - public Block getBlock() { - return block; - } - - @Override - public boolean canHaveLazyContinuationLines() { - return true; - } - - @Override - public BlockContinue tryContinue(ParserState parserState) { - // We're not continuing to give other block parsers a chance to interrupt this definition. - // But if no other block parser applied (including another FootnotesBlockParser), we will - // accept the line via lazy continuation. - return BlockContinue.none(); - } - - public static class Factory implements BlockParserFactory { - - @Override - public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { - var content = state.getLine().getContent(); - // TODO: Can it be indented? Maybe less than code block indent. - var index = state.getNextNonSpaceIndex(); - if (content.charAt(index) != '[' || index + 1 >= content.length()) { - return BlockStart.none(); - } - index++; - if (content.charAt(index) != '^' || index + 1 >= content.length()) { - return BlockStart.none(); - } - // Now at first label character (if any) - index++; - - for (int i = index; i < content.length(); i++) { - var c = content.charAt(i); - if (c == ']') { - if (i > index) { - var label = content.subSequence(index, i).toString(); - return BlockStart.of(new FootnoteBlockParser(label)); - } else { - return BlockStart.none(); - } - } - // TODO: Check what GitHub actually does here, e.g. tabs, control characters, other Unicode whitespace - if (Character.isWhitespace(c)) { - return BlockStart.none(); - } - } - - return BlockStart.none(); - } - } - } - - private final Parser PARSER = Parser.builder().extensions(List.of(FootnotesExtension.create())).build(); - - @Test - public void testBlockStart() { - for (var s : List.of("1", "a")) { - var doc = PARSER.parse("[^" + s + "]: footnote\n"); - var def = Nodes.find(doc, FootnoteDefinition.class); - // TODO: Should label be "^1" instead? - assertEquals(s, def.getLabel()); - } - - for (var s : List.of("", " ", "a b")) { - var doc = PARSER.parse("[^" + s + "]: footnote\n"); - assertNull(Nodes.tryFind(doc, FootnoteDefinition.class)); - } - - // TODO: Test what characters are allowed for the label, e.g. - // [^], [^ ], [^^], [^[], [^*], [^\], [^\a], [^🙂], tab?, [^&], [^&] - } - - @Test - public void testBlockStartInterrupts() { - var doc = PARSER.parse("test\n[^1]: footnote\n"); - var paragraph = Nodes.find(doc, Paragraph.class); - var def = Nodes.find(doc, FootnoteDefinition.class); - assertEquals("test", ((Text) paragraph.getLastChild()).getLiteral()); - assertEquals("1", def.getLabel()); - } - - @Test - public void testMultiple() { - var doc = PARSER.parse("[^1]: foo\n[^2]: bar\n"); - var def1 = (FootnoteDefinition) doc.getFirstChild(); - var def2 = (FootnoteDefinition) doc.getLastChild(); - assertEquals("1", def1.getLabel()); - assertEquals("2", def2.getLabel()); - } - - @Test - public void testBlockStartAfterLinkReferenceDefinition() { - var doc = PARSER.parse("[foo]: /url\n[^1]: footnote\n"); - var linkReferenceDef = Nodes.find(doc, LinkReferenceDefinition.class); - var footnotesDef = Nodes.find(doc, FootnoteDefinition.class); - assertEquals("foo", linkReferenceDef.getLabel()); - assertEquals("1", footnotesDef.getLabel()); - } - - @Test - public void testBlockContinue() { - var doc = PARSER.parse("[^1]: footnote\nstill\n"); - var def = Nodes.find(doc, FootnoteDefinition.class); - assertEquals("1", def.getLabel()); - assertNull(Nodes.tryFind(doc, Paragraph.class)); - } - - @Test - public void testFootnotesDefinitionInterruptedByOthers() { - var doc = PARSER.parse("[^1]: footnote\n# Heading\n"); - var def = Nodes.find(doc, FootnoteDefinition.class); - var heading = Nodes.find(doc, Heading.class); - assertEquals("1", def.getLabel()); - assertEquals("Heading", ((Text) heading.getFirstChild()).getLiteral()); - } -} diff --git a/commonmark/src/test/java/org/commonmark/test/Nodes.java b/commonmark/src/test/java/org/commonmark/test/Nodes.java index 8db504924..06d04fde6 100644 --- a/commonmark/src/test/java/org/commonmark/test/Nodes.java +++ b/commonmark/src/test/java/org/commonmark/test/Nodes.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class Nodes { @@ -43,10 +44,7 @@ public static T tryFind(Node parent, Class nodeClass) { * could not be found. */ public static T find(Node parent, Class nodeClass) { - var node = tryFind(parent, nodeClass); - if (node == null) { - throw new IllegalArgumentException("Could not find a " + nodeClass.getSimpleName() + " node in " + parent); - } - return node; + return Objects.requireNonNull(tryFind(parent, nodeClass), + "Could not find a " + nodeClass.getSimpleName() + " node in " + parent); } } diff --git a/pom.xml b/pom.xml index 465c3b8e4..32003fdf7 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ commonmark commonmark-ext-autolink + commonmark-ext-footnotes commonmark-ext-gfm-strikethrough commonmark-ext-gfm-tables commonmark-ext-heading-anchor @@ -63,7 +64,7 @@ false - http://static.javadoc.io/org.commonmark/commonmark/${project.version}/ + https://static.javadoc.io/org.commonmark/commonmark/${project.version}/ ${commonmark.javadoc.location} From 0c17ae84c059a4f8e95329e351e0a53d06c69ced Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 4 May 2024 15:36:45 +1000 Subject: [PATCH 104/345] Refactor link/image parsing into more manageable pieces --- .../java/org/commonmark/internal/Bracket.java | 2 +- .../commonmark/internal/InlineParserImpl.java | 214 +++++++++++------- 2 files changed, 127 insertions(+), 89 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/Bracket.java b/commonmark/src/main/java/org/commonmark/internal/Bracket.java index 9c73a1327..2a91b877e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Bracket.java +++ b/commonmark/src/main/java/org/commonmark/internal/Bracket.java @@ -41,7 +41,7 @@ public class Bracket { public boolean allowed = true; /** - * Whether there is an unescaped bracket (opening or closing) anywhere after this opening bracket. + * Whether there is an unescaped bracket (opening or closing) after this opening bracket in the text parsed so far. */ public boolean bracketAfter = false; diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 5b91a5a16..fdbcb388b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -7,8 +7,8 @@ import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.SourceLines; -import org.commonmark.parser.beta.*; import org.commonmark.parser.beta.Scanner; +import org.commonmark.parser.beta.*; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.text.Characters; @@ -295,107 +295,103 @@ private Node parseCloseBracket() { } if (!opener.allowed) { - // Matching opener but it's not allowed, just return a literal. + // Matching opener, but it's not allowed, just return a literal. removeLastBracket(); return text(scanner.getSource(beforeClose, afterClose)); } - // Check to see if we have a link/image - String dest = null; - String title = null; - - // Maybe a inline link like `[foo](/uri "title")` - if (scanner.next('(')) { - scanner.whitespace(); - dest = parseLinkDestination(scanner); - if (dest == null) { - scanner.setPosition(afterClose); - } else { - int whitespace = scanner.whitespace(); - // title needs a whitespace before - if (whitespace >= 1) { - title = parseLinkTitle(scanner); - scanner.whitespace(); - } - if (!scanner.next(')')) { - // Don't have a closing `)`, so it's not a destination and title -> reset. - // Note that something like `[foo](` could be valid, `(` will just be text. - scanner.setPosition(afterClose); - dest = null; - title = null; - } - } + var linkOrImage = parseLinkOrImage(opener, beforeClose); + if (linkOrImage != null) { + return linkOrImage; } + scanner.setPosition(afterClose); - // Maybe a reference link like `[foo][bar]`, `[foo][]` or `[foo]`. - // Note that even `[foo](` could be a valid link if there's a reference, which is why this is not just an `else` - // here. - if (dest == null) { - // See if there's a link label like `[bar]` or `[]` - String ref = parseLinkLabel(scanner); - if (ref == null) { - scanner.setPosition(afterClose); - } - if ((ref == null || ref.isEmpty()) && !opener.bracketAfter) { - // If the second label is empty `[foo][]` or missing `[foo]`, then the first label is the reference. - // But it can only be a reference when there's no (unescaped) bracket in it. - // If there is, we don't even need to try to look up the reference. This is an optimization. - ref = scanner.getSource(opener.contentPosition, beforeClose).getContent(); - } + // Nothing parsed, just parse the bracket as text and continue + removeLastBracket(); + return text(scanner.getSource(beforeClose, afterClose)); + } - if (ref != null) { - LinkReferenceDefinition definition = context.getLinkReferenceDefinition(ref); - if (definition != null) { - dest = definition.getDestination(); - title = definition.getTitle(); - } + private Node parseLinkOrImage(Bracket opener, Position beforeClose) { + // Check to see if we have a link (or image, with a ! in front). The different types: + // - Inline: `[foo](/uri)` or with optional title `[foo](/uri "title")` + // - Reference links + // - Full: `[foo][bar]` (foo is the text and bar is the label that needs to match a reference) + // - Collapsed: `[foo][]` (foo is both the text and label) + // - Shortcut: `[foo]` (foo is both the text and label) + + // Starting position is after the closing `]` + Position afterClose = scanner.position(); + + // Maybe an inline link/image + var destinationTitle = parseInlineDestinationTitle(scanner); + if (destinationTitle != null) { + var linkOrImage = opener.image + ? new Image(destinationTitle.destination, destinationTitle.title) + : new Link(destinationTitle.destination, destinationTitle.title); + return processLinkOrImage(opener, linkOrImage); + } + // Not an inline link/image, rewind back to after `]`. + scanner.setPosition(afterClose); + + // Maybe a reference link/image like `[foo][bar]`, `[foo][]` or `[foo]`. + // Note that even `[foo](` could be a valid link if foo is a reference, which is why we try this even if the `(` + // failed to be parsed as an inline link/image before. + + // See if there's a link label like `[bar]` or `[]` + String ref = parseLinkLabel(scanner); + if ((ref == null || ref.isEmpty()) && !opener.bracketAfter) { + // If the second label is empty `[foo][]` or missing `[foo]`, then the first label is the reference. + // But it can only be a reference when there's no (unescaped) bracket in it. + // If there is, we don't even need to try to look up the reference. This is an optimization. + ref = scanner.getSource(opener.contentPosition, beforeClose).getContent(); + } + + if (ref != null) { + LinkReferenceDefinition definition = context.getLinkReferenceDefinition(ref); + if (definition != null) { + var linkOrImage = opener.image + ? new Image(definition.getDestination(), definition.getTitle()) + : new Link(definition.getDestination(), definition.getTitle()); + return processLinkOrImage(opener, linkOrImage); } } - if (dest != null) { - // If we got here, we have a link or image - Node linkOrImage = opener.image ? new Image(dest, title) : new Link(dest, title); + return null; + } - // Add all nodes between the opening bracket and now (closing bracket) as child nodes of the link - Node node = opener.node.getNext(); - while (node != null) { - Node next = node.getNext(); - linkOrImage.appendChild(node); - node = next; - } + private Node processLinkOrImage(Bracket opener, Node linkOrImage) { + // Add all nodes between the opening bracket and now (closing bracket) as child nodes of the link + Node node = opener.node.getNext(); + while (node != null) { + Node next = node.getNext(); + linkOrImage.appendChild(node); + node = next; + } - if (includeSourceSpans) { - linkOrImage.setSourceSpans(scanner.getSource(opener.markerPosition, scanner.position()).getSourceSpans()); - } + if (includeSourceSpans) { + linkOrImage.setSourceSpans(scanner.getSource(opener.markerPosition, scanner.position()).getSourceSpans()); + } - // Process delimiters such as emphasis inside link/image - processDelimiters(opener.previousDelimiter); - mergeChildTextNodes(linkOrImage); - // We don't need the corresponding text node anymore, we turned it into a link/image node - opener.node.unlink(); - removeLastBracket(); + // Process delimiters such as emphasis inside link/image + processDelimiters(opener.previousDelimiter); + mergeChildTextNodes(linkOrImage); + // We don't need the corresponding text node anymore, we turned it into a link/image node + opener.node.unlink(); + removeLastBracket(); - // Links within links are not allowed. We found this link, so there can be no other link around it. - if (!opener.image) { - Bracket bracket = lastBracket; - while (bracket != null) { - if (!bracket.image) { - // Disallow link opener. It will still get matched, but will not result in a link. - bracket.allowed = false; - } - bracket = bracket.previous; + // Links within links are not allowed. We found this link, so there can be no other link around it. + if (!opener.image) { + Bracket bracket = lastBracket; + while (bracket != null) { + if (!bracket.image) { + // Disallow link opener. It will still get matched, but will not result in a link. + bracket.allowed = false; } + bracket = bracket.previous; } - - return linkOrImage; - - } else { - // No link or image, parse just the bracket as text and continue - removeLastBracket(); - - scanner.setPosition(afterClose); - return text(scanner.getSource(beforeClose, afterClose)); } + + return linkOrImage; } private void addBracket(Bracket bracket) { @@ -409,10 +405,39 @@ private void removeLastBracket() { lastBracket = lastBracket.previous; } + /** + * Try to parse the destination and an optional title for an inline link/image. + */ + private static DestinationTitle parseInlineDestinationTitle(Scanner scanner) { + if (!scanner.next('(')) { + return null; + } + + scanner.whitespace(); + String dest = parseLinkDestination(scanner); + if (dest == null) { + return null; + } + + String title = null; + int whitespace = scanner.whitespace(); + // title needs a whitespace before + if (whitespace >= 1) { + title = parseLinkTitle(scanner); + scanner.whitespace(); + } + if (!scanner.next(')')) { + // Don't have a closing `)`, so it's not a destination and title. + // Note that something like `[foo](` could still be valid later, `(` will just be text. + return null; + } + return new DestinationTitle(dest, title); + } + /** * Attempt to parse link destination, returning the string or null if no match. */ - private String parseLinkDestination(Scanner scanner) { + private static String parseLinkDestination(Scanner scanner) { char delimiter = scanner.peek(); Position start = scanner.position(); if (!LinkScanner.scanLinkDestination(scanner)) { @@ -434,7 +459,7 @@ private String parseLinkDestination(Scanner scanner) { /** * Attempt to parse link title (sans quotes), returning the string or null if no match. */ - private String parseLinkTitle(Scanner scanner) { + private static String parseLinkTitle(Scanner scanner) { Position start = scanner.position(); if (!LinkScanner.scanLinkTitle(scanner)) { return null; @@ -449,7 +474,7 @@ private String parseLinkTitle(Scanner scanner) { /** * Attempt to parse a link label, returning the label between the brackets or null. */ - String parseLinkLabel(Scanner scanner) { + static String parseLinkLabel(Scanner scanner) { if (!scanner.next('[')) { return null; } @@ -772,4 +797,17 @@ private static class DelimiterData { this.canClose = canClose; } } + + /** + * A destination and optional title for a link or image. + */ + private static class DestinationTitle { + final String destination; + final String title; + + public DestinationTitle(String destination, String title) { + this.destination = destination; + this.title = title; + } + } } From 868646e54cd6624e89623d26fec44f09e0f68a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=ED=98=81?= <94831670+kmh916@users.noreply.github.com> Date: Tue, 14 May 2024 21:42:55 +0900 Subject: [PATCH 105/345] README: Bump version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e2acc08b7..3131e2182 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.21.0 + 0.22.0 ``` @@ -265,7 +265,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.21.0 + 0.22.0 ``` From 79e1aed27f9021590357d2e0f82f7ccccea3f577 Mon Sep 17 00:00:00 2001 From: Andy Zhang <37402126+AnzhiZhang@users.noreply.github.com> Date: Tue, 14 May 2024 23:44:02 +0100 Subject: [PATCH 106/345] Add data to DefaultUrlSanitizer protocols --- .../org/commonmark/renderer/html/DefaultUrlSanitizer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java b/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java index 032b8ef2e..4c5bed12c 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java @@ -4,7 +4,7 @@ /** * - * Allows http, https and mailto protocols for url. + * Allows http, https, mailto, and data protocols for url. * Also allows protocol relative urls, and relative urls. * Implementation based on https://github.com/OWASP/java-html-sanitizer/blob/f07e44b034a45d94d6fd010279073c38b6933072/src/main/java/org/owasp/html/FilterUrlByProtocolAttributePolicy.java */ @@ -12,7 +12,7 @@ public class DefaultUrlSanitizer implements UrlSanitizer { private Set protocols; public DefaultUrlSanitizer() { - this(List.of("http", "https", "mailto")); + this(List.of("http", "https", "mailto", "data")); } public DefaultUrlSanitizer(Collection protocols) { From 1fa49f469ef7113ad4e0ee40bafed01eb32c4dd9 Mon Sep 17 00:00:00 2001 From: kmh916 Date: Wed, 15 May 2024 14:20:51 +0900 Subject: [PATCH 107/345] Update reference spec version in comments to 0.31.2 --- .../src/main/java/org/commonmark/node/FencedCodeBlock.java | 2 +- commonmark/src/main/java/org/commonmark/node/HtmlBlock.java | 2 +- commonmark/src/main/java/org/commonmark/node/HtmlInline.java | 2 +- commonmark/src/main/java/org/commonmark/node/Link.java | 2 +- .../main/java/org/commonmark/node/LinkReferenceDefinition.java | 2 +- commonmark/src/main/java/org/commonmark/node/ListBlock.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java b/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java index 205ef9126..314c7457d 100644 --- a/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java @@ -67,7 +67,7 @@ public void setFenceIndent(int fenceIndent) { } /** - * @see CommonMark spec + * @see CommonMark spec */ public String getInfo() { return info; diff --git a/commonmark/src/main/java/org/commonmark/node/HtmlBlock.java b/commonmark/src/main/java/org/commonmark/node/HtmlBlock.java index ad46c56ce..fbe00927d 100644 --- a/commonmark/src/main/java/org/commonmark/node/HtmlBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/HtmlBlock.java @@ -3,7 +3,7 @@ /** * HTML block * - * @see CommonMark Spec + * @see CommonMark Spec */ public class HtmlBlock extends Block { diff --git a/commonmark/src/main/java/org/commonmark/node/HtmlInline.java b/commonmark/src/main/java/org/commonmark/node/HtmlInline.java index 291fcde3c..35360c639 100644 --- a/commonmark/src/main/java/org/commonmark/node/HtmlInline.java +++ b/commonmark/src/main/java/org/commonmark/node/HtmlInline.java @@ -3,7 +3,7 @@ /** * Inline HTML element. * - * @see CommonMark Spec + * @see CommonMark Spec */ public class HtmlInline extends Node { diff --git a/commonmark/src/main/java/org/commonmark/node/Link.java b/commonmark/src/main/java/org/commonmark/node/Link.java index b2ed8c2a1..f0a5ed0d8 100644 --- a/commonmark/src/main/java/org/commonmark/node/Link.java +++ b/commonmark/src/main/java/org/commonmark/node/Link.java @@ -18,7 +18,7 @@ * Note that the text in the link can contain inline formatting, so it could also contain an {@link Image} or * {@link Emphasis}, etc. * - * @see CommonMark Spec for links + * @see CommonMark Spec for links */ public class Link extends Node { diff --git a/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java b/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java index e63b0242a..412a3c38d 100644 --- a/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java +++ b/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java @@ -9,7 +9,7 @@ * They can be referenced anywhere else in the document to produce a link using [foo]. The definitions * themselves are usually not rendered in the final output. * - * @see Link reference definitions + * @see Link reference definitions */ public class LinkReferenceDefinition extends Block { diff --git a/commonmark/src/main/java/org/commonmark/node/ListBlock.java b/commonmark/src/main/java/org/commonmark/node/ListBlock.java index 69482f66e..1a7c8b2fd 100644 --- a/commonmark/src/main/java/org/commonmark/node/ListBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/ListBlock.java @@ -6,7 +6,7 @@ public abstract class ListBlock extends Block { /** * @return whether this list is tight or loose - * @see CommonMark Spec for tight lists + * @see CommonMark Spec for tight lists */ public boolean isTight() { return tight; From 92ef1d0a9f72ee24741b1da72c0ab07c06db7ad2 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 18 May 2024 11:35:09 +1000 Subject: [PATCH 108/345] Make bracket processing extensible via a BracketProcessor --- .../commonmark/internal/InlineParserImpl.java | 92 ++++++++++++++++--- .../internal/inline/BracketResultImpl.java | 47 ++++++++++ .../internal/inline/CoreBracketProcessor.java | 27 ++++++ .../commonmark/parser/beta/BracketInfo.java | 27 ++++++ .../parser/beta/BracketProcessor.java | 8 ++ .../commonmark/parser/beta/BracketResult.java | 20 ++++ 6 files changed, 206 insertions(+), 15 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/BracketResultImpl.java create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java create mode 100644 commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java create mode 100644 commonmark/src/main/java/org/commonmark/parser/beta/BracketProcessor.java create mode 100644 commonmark/src/main/java/org/commonmark/parser/beta/BracketResult.java diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index fdbcb388b..fcc37692f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -337,22 +337,84 @@ private Node parseLinkOrImage(Bracket opener, Position beforeClose) { // Note that even `[foo](` could be a valid link if foo is a reference, which is why we try this even if the `(` // failed to be parsed as an inline link/image before. + String text = scanner.getSource(opener.contentPosition, beforeClose).getContent(); + // See if there's a link label like `[bar]` or `[]` - String ref = parseLinkLabel(scanner); - if ((ref == null || ref.isEmpty()) && !opener.bracketAfter) { - // If the second label is empty `[foo][]` or missing `[foo]`, then the first label is the reference. - // But it can only be a reference when there's no (unescaped) bracket in it. - // If there is, we don't even need to try to look up the reference. This is an optimization. - ref = scanner.getSource(opener.contentPosition, beforeClose).getContent(); - } - - if (ref != null) { - LinkReferenceDefinition definition = context.getLinkReferenceDefinition(ref); - if (definition != null) { - var linkOrImage = opener.image - ? new Image(definition.getDestination(), definition.getTitle()) - : new Link(definition.getDestination(), definition.getTitle()); - return processLinkOrImage(opener, linkOrImage); + String label = parseLinkLabel(scanner); + if (label == null) { + // No label, rewind back + scanner.setPosition(afterClose); + } + var referenceType = label == null ? + BracketInfo.ReferenceType.SHORTCUT : label.isEmpty() ? + BracketInfo.ReferenceType.COLLAPSED : + BracketInfo.ReferenceType.FULL; + if ((referenceType == BracketInfo.ReferenceType.SHORTCUT || referenceType == BracketInfo.ReferenceType.COLLAPSED) + && opener.bracketAfter) { + // In case of SHORTCUT or COLLAPSED, the text is used as the reference. But the reference is not allowed to + // contain an unescaped bracket, so if that's the case we don't need to continue. This is an optimization. + return null; + } + + var bracketInfo = new BracketInfo() { + @Override + public OpenerType openerType() { + return opener.image ? OpenerType.IMAGE : OpenerType.LINK; + } + + @Override + public ReferenceType referenceType() { + return referenceType; + } + + @Override + public String text() { + return text; + } + + @Override + public String label() { + return label; + } + + @Override + public Position afterTextBracket() { + return afterClose; + } + }; + + // TODO: Configurable and multiple processors + // TODO: Reset scanner on fail + // TODO: Should inline links also go through this, maybe? That would allow e.g. the image attributes extension + // to use it to parse the attributes, I think. It would also be clearer: Every type of link goes through this. + var bracketResult = new CoreBracketProcessor().process(bracketInfo, scanner, context); + if (bracketResult instanceof BracketResultImpl) { + var type = ((BracketResultImpl) bracketResult).getType(); + var node = ((BracketResultImpl) bracketResult).getNode(); + var position = ((BracketResultImpl) bracketResult).getPosition(); + var startFromBracket = ((BracketResultImpl) bracketResult).isStartFromBracket(); + + switch (type) { + case WRAP: + scanner.setPosition(position); + // TODO: startFromBracket + return processLinkOrImage(opener, node); + case REPLACE: + scanner.setPosition(position); + + // Remove delimiters (but keep text nodes) + while (lastDelimiter != null && lastDelimiter != opener.previousDelimiter) { + removeDelimiterKeepNode(lastDelimiter); + } + + removeLastBracket(); + + // TODO: startFromBracket needs to split the opening node.. Maybe we should just keep ! and [ + // as separate nodes in Bracket + for (Node n = opener.node; n != null; n = n.getNext()) { + n.unlink(); + } + return node; } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BracketResultImpl.java b/commonmark/src/main/java/org/commonmark/internal/inline/BracketResultImpl.java new file mode 100644 index 000000000..1ae450c9f --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BracketResultImpl.java @@ -0,0 +1,47 @@ +package org.commonmark.internal.inline; + +import org.commonmark.internal.InlineParserImpl; +import org.commonmark.node.Node; +import org.commonmark.parser.beta.BracketResult; +import org.commonmark.parser.beta.Position; + +public class BracketResultImpl implements BracketResult { + @Override + public BracketResult startFromBracket() { + startFromBracket = true; + return this; + } + + public enum Type { + WRAP, + REPLACE + } + + private final Type type; + private final Node node; + private final Position position; + + private boolean startFromBracket; + + public BracketResultImpl(Type type, Node node, Position position) { + this.type = type; + this.node = node; + this.position = position; + } + + public Type getType() { + return type; + } + + public Node getNode() { + return node; + } + + public Position getPosition() { + return position; + } + + public boolean isStartFromBracket() { + return startFromBracket; + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java new file mode 100644 index 000000000..f208f76e2 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java @@ -0,0 +1,27 @@ +package org.commonmark.internal.inline; + +import org.commonmark.node.Image; +import org.commonmark.node.Link; +import org.commonmark.parser.InlineParserContext; +import org.commonmark.parser.beta.BracketInfo; +import org.commonmark.parser.beta.BracketProcessor; +import org.commonmark.parser.beta.BracketResult; +import org.commonmark.parser.beta.Scanner; + +public class CoreBracketProcessor implements BracketProcessor { + + @Override + public BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlineParserContext context) { + var label = bracketInfo.label(); + var ref = label != null && !label.isEmpty() ? label : bracketInfo.text(); + var def = context.getLinkReferenceDefinition(ref); + if (def != null) { + if (bracketInfo.openerType() == BracketInfo.OpenerType.IMAGE) { + return BracketResult.wrapTextIn(new Image(def.getDestination(), def.getTitle()), scanner.position()); + } else if (bracketInfo.openerType() == BracketInfo.OpenerType.LINK) { + return BracketResult.wrapTextIn(new Link(def.getDestination(), def.getTitle()), scanner.position()); + } + } + return BracketResult.none(); + } +} diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java b/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java new file mode 100644 index 000000000..7dc49ea50 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java @@ -0,0 +1,27 @@ +package org.commonmark.parser.beta; + +public interface BracketInfo { + enum OpenerType { + // An image (a `!` before the `[`) + IMAGE, + // A link + LINK + } + + enum ReferenceType { + FULL, + COLLAPSED, + SHORTCUT + } + + // TODO: We could also expose the opener Text (`[` or `![`) + OpenerType openerType(); + + ReferenceType referenceType(); + + String text(); + + String label(); + + Position afterTextBracket(); +} diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/BracketProcessor.java b/commonmark/src/main/java/org/commonmark/parser/beta/BracketProcessor.java new file mode 100644 index 000000000..5d7aae236 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/beta/BracketProcessor.java @@ -0,0 +1,8 @@ +package org.commonmark.parser.beta; + +import org.commonmark.parser.InlineParserContext; + +public interface BracketProcessor { + + BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlineParserContext context); +} diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/BracketResult.java b/commonmark/src/main/java/org/commonmark/parser/beta/BracketResult.java new file mode 100644 index 000000000..6a3a23cef --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/beta/BracketResult.java @@ -0,0 +1,20 @@ +package org.commonmark.parser.beta; + +import org.commonmark.internal.inline.BracketResultImpl; +import org.commonmark.node.Node; + +public interface BracketResult { + static BracketResult none() { + return null; + } + + static BracketResult wrapTextIn(Node node, Position position) { + return new BracketResultImpl(BracketResultImpl.Type.WRAP, node, position); + } + + static BracketResult replaceWith(Node node, Position position) { + return new BracketResultImpl(BracketResultImpl.Type.WRAP, node, position); + } + + BracketResult startFromBracket(); +} From 7500905e270b66df75a1a1493a6e9cdb84dc321a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 18 May 2024 15:31:38 +1000 Subject: [PATCH 109/345] Allow adding BracketProcessors, use for footnotes extension --- .../ext/footnotes/FootnoteReference.java | 15 +++++ .../ext/footnotes/FootnotesExtension.java | 5 +- .../internal/FootnoteBracketProcessor.java | 26 +++++++++ .../ext/footnotes/FootnotesTest.java | 57 +++++++++++++++++++ .../commonmark/internal/DocumentParser.java | 11 ++-- .../internal/InlineParserContextImpl.java | 11 +++- .../commonmark/internal/InlineParserImpl.java | 39 +++++++++---- .../parser/InlineParserContext.java | 6 ++ .../java/org/commonmark/parser/Parser.java | 20 ++++++- .../commonmark/parser/beta/BracketInfo.java | 6 ++ .../test/InlineParserContextTest.java | 6 ++ 11 files changed, 182 insertions(+), 20 deletions(-) create mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteReference.java create mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteReference.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteReference.java new file mode 100644 index 000000000..d7eda870c --- /dev/null +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteReference.java @@ -0,0 +1,15 @@ +package org.commonmark.ext.footnotes; + +import org.commonmark.node.CustomNode; + +public class FootnoteReference extends CustomNode { + private String label; + + public FootnoteReference(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } +} diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java index df376aaba..60b72393a 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java @@ -2,6 +2,7 @@ import org.commonmark.Extension; import org.commonmark.ext.footnotes.internal.FootnoteBlockParser; +import org.commonmark.ext.footnotes.internal.FootnoteBracketProcessor; import org.commonmark.parser.Parser; /** @@ -19,6 +20,8 @@ public static Extension create() { @Override public void extend(Parser.Builder parserBuilder) { - parserBuilder.customBlockParserFactory(new FootnoteBlockParser.Factory()); + parserBuilder + .customBlockParserFactory(new FootnoteBlockParser.Factory()) + .bracketProcessor(new FootnoteBracketProcessor()); } } diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java new file mode 100644 index 000000000..6fa4ac3ee --- /dev/null +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java @@ -0,0 +1,26 @@ +package org.commonmark.ext.footnotes.internal; + +import org.commonmark.ext.footnotes.FootnoteReference; +import org.commonmark.parser.InlineParserContext; +import org.commonmark.parser.beta.BracketInfo; +import org.commonmark.parser.beta.BracketProcessor; +import org.commonmark.parser.beta.BracketResult; +import org.commonmark.parser.beta.Scanner; + +public class FootnoteBracketProcessor implements BracketProcessor { + @Override + public BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlineParserContext context) { + // TODO: Does parsing need to be more strict here? + var text = bracketInfo.text(); + if (text.startsWith("^")) { + // TODO: Do we need to check if a definition exists before doing this? (That would be the same as reference + // links.) + + // For footnotes, we only ever consume the text part of the link, not the label part (if any). + var position = bracketInfo.afterTextBracket(); + var label = text.substring(1); + return BracketResult.replaceWith(new FootnoteReference(label), position); + } + return BracketResult.none(); + } +} diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index c6fea7277..92e51b6da 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -79,6 +79,63 @@ public void testFootnotesDefinitionInterruptedByOthers() { assertEquals("Heading", ((Text) heading.getFirstChild()).getLiteral()); } + @Test + public void testReference() { + var doc = PARSER.parse("Test [^foo]\n\n[^foo]: /url\n"); + var ref = find(doc, FootnoteReference.class); + assertEquals("foo", ref.getLabel()); + } + + // Interesting test cases: + + // Test [foo][^bar] + // + // [^bar]: footnote + // + // -> [^bar] is a footnote but [foo] is just text, because full reference links don't resolve as footnotes + + // Test [^bar][] + // + // [^bar]: footnote + // + // -> [^bar] is a footnote but [] is just text, because collapsed reference links don't resolve as footnotes + + // Test [^f[oo] + // + // [^f[oo]: /url + // + // -> Not a footnote, [ needs to be escaped + + // Test [^*foo*] + // + // [^*foo*]: /url + // + // -> No emphasis inside footnote reference + + // Test *abc [^foo*] def* + // + // [^foo*]: /url + // + // -> Emphasis around footnote reference + + // Test [^*foo*][foo] + // + // [^*foo*]: /url + // + // [foo]: /url + // + // vs + // + // Test [^*foo*][foo] + // + // [^*foo*]: /url + + // Test ![^foo] + // + // [^foo]: note + // + // -> Not an image + private static T find(Node parent, Class nodeClass) { return Objects.requireNonNull(tryFind(parent, nodeClass), "Could not find a " + nodeClass.getSimpleName() + " node in " + parent); } diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index dd59b1a36..e3be3cfa3 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -3,6 +3,7 @@ import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.*; +import org.commonmark.parser.beta.BracketProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.block.*; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -69,6 +70,7 @@ public class DocumentParser implements ParserState { private final InlineParserFactory inlineParserFactory; private final List inlineContentParserFactories; private final List delimiterProcessors; + private final List bracketProcessors; private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; private final LinkReferenceDefinitions definitions = new LinkReferenceDefinitions(); @@ -78,11 +80,12 @@ public class DocumentParser implements ParserState { public DocumentParser(List blockParserFactories, InlineParserFactory inlineParserFactory, List inlineContentParserFactories, List delimiterProcessors, - IncludeSourceSpans includeSourceSpans) { + List bracketProcessors, IncludeSourceSpans includeSourceSpans) { this.blockParserFactories = blockParserFactories; this.inlineParserFactory = inlineParserFactory; this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; + this.bracketProcessors = bracketProcessors; this.includeSourceSpans = includeSourceSpans; this.documentBlockParser = new DocumentBlockParser(); @@ -481,10 +484,10 @@ private void addDefinitionsFrom(ParagraphParser paragraphParser) { * Walk through a block & children recursively, parsing string content into inline content where appropriate. */ private void processInlines() { - InlineParserContextImpl context = new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, definitions); - InlineParser inlineParser = inlineParserFactory.create(context); + var context = new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, bracketProcessors, definitions); + var inlineParser = inlineParserFactory.create(context); - for (BlockParser blockParser : allBlockParsers) { + for (var blockParser : allBlockParsers) { blockParser.parseInlines(inlineParser); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java index 689a5372e..d7bf8d16b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java @@ -1,8 +1,9 @@ package org.commonmark.internal; -import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParserContext; +import org.commonmark.parser.beta.BracketProcessor; +import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; import java.util.List; @@ -11,13 +12,16 @@ public class InlineParserContextImpl implements InlineParserContext { private final List inlineContentParserFactories; private final List delimiterProcessors; + private final List bracketProcessors; private final LinkReferenceDefinitions linkReferenceDefinitions; public InlineParserContextImpl(List inlineContentParserFactories, List delimiterProcessors, + List bracketProcessors, LinkReferenceDefinitions linkReferenceDefinitions) { this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; + this.bracketProcessors = bracketProcessors; this.linkReferenceDefinitions = linkReferenceDefinitions; } @@ -31,6 +35,11 @@ public List getCustomDelimiterProcessors() { return delimiterProcessors; } + @Override + public List getCustomBracketProcessors() { + return bracketProcessors; + } + @Override public LinkReferenceDefinition getLinkReferenceDefinition(String label) { return linkReferenceDefinitions.get(label); diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index fcc37692f..6e4ad9c61 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -19,6 +19,7 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private final InlineParserContext context; private final List inlineContentParserFactories; private final Map delimiterProcessors; + private final List bracketProcessors; private final BitSet specialCharacters; private Map> inlineParsers; @@ -41,6 +42,7 @@ public InlineParserImpl(InlineParserContext context) { this.context = context; this.inlineContentParserFactories = calculateInlineContentParserFactories(context.getCustomInlineContentParserFactories()); this.delimiterProcessors = calculateDelimiterProcessors(context.getCustomDelimiterProcessors()); + this.bracketProcessors = calculateBracketProcessors(context.getCustomBracketProcessors()); this.specialCharacters = calculateSpecialCharacters(this.delimiterProcessors.keySet(), this.inlineContentParserFactories); } @@ -55,6 +57,13 @@ private List calculateInlineContentParserFactories(L return list; } + private List calculateBracketProcessors(List bracketProcessors) { + // Custom bracket processors can override the built-in behavior, so make sure they are tried first + var list = new ArrayList<>(bracketProcessors); + list.add(new CoreBracketProcessor()); + return list; + } + private static Map calculateDelimiterProcessors(List delimiterProcessors) { var map = new HashMap(); addDelimiterProcessors(List.of(new AsteriskDelimiterProcessor(), new UnderscoreDelimiterProcessor()), map); @@ -383,18 +392,24 @@ public Position afterTextBracket() { } }; - // TODO: Configurable and multiple processors - // TODO: Reset scanner on fail - // TODO: Should inline links also go through this, maybe? That would allow e.g. the image attributes extension - // to use it to parse the attributes, I think. It would also be clearer: Every type of link goes through this. - var bracketResult = new CoreBracketProcessor().process(bracketInfo, scanner, context); - if (bracketResult instanceof BracketResultImpl) { - var type = ((BracketResultImpl) bracketResult).getType(); - var node = ((BracketResultImpl) bracketResult).getNode(); - var position = ((BracketResultImpl) bracketResult).getPosition(); - var startFromBracket = ((BracketResultImpl) bracketResult).isStartFromBracket(); - - switch (type) { + var processorStartPosition = scanner.position(); + + for (var bracketProcessor : bracketProcessors) { + // TODO: Should inline links also go through this, maybe? That would allow e.g. the image attributes extension + // to use it to parse the attributes, I think. It would also be clearer: Every type of link goes through this. + var bracketResult = bracketProcessor.process(bracketInfo, scanner, context); + if (!(bracketResult instanceof BracketResultImpl)) { + // Reset position in case the processor used the scanner, and it didn't work out. + scanner.setPosition(processorStartPosition); + continue; + } + + var result = (BracketResultImpl) bracketResult; + var node = result.getNode(); + var position = result.getPosition(); + var startFromBracket = result.isStartFromBracket(); + + switch (result.getType()) { case WRAP: scanner.setPosition(position); // TODO: startFromBracket diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java index dde86b311..6c3c9cbc6 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -1,6 +1,7 @@ package org.commonmark.parser; import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.parser.beta.BracketProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -23,6 +24,11 @@ public interface InlineParserContext { */ List getCustomDelimiterProcessors(); + /** + * TODO + */ + List getCustomBracketProcessors(); + /** * Look up a {@link LinkReferenceDefinition} for a given label. *

      diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 917534181..dc91f5dc3 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -6,6 +6,7 @@ import org.commonmark.internal.InlineParserImpl; import org.commonmark.internal.LinkReferenceDefinitions; import org.commonmark.node.*; +import org.commonmark.parser.beta.BracketProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.block.BlockParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -32,6 +33,7 @@ public class Parser { private final List blockParserFactories; private final List inlineContentParserFactories; private final List delimiterProcessors; + private final List bracketProcessors; private final InlineParserFactory inlineParserFactory; private final List postProcessors; private final IncludeSourceSpans includeSourceSpans; @@ -42,11 +44,14 @@ private Parser(Builder builder) { this.postProcessors = builder.postProcessors; this.inlineContentParserFactories = builder.inlineContentParserFactories; this.delimiterProcessors = builder.delimiterProcessors; + this.bracketProcessors = builder.bracketProcessors; this.includeSourceSpans = builder.includeSourceSpans; // Try to construct an inline parser. Invalid configuration might result in an exception, which we want to // detect as soon as possible. - this.inlineParserFactory.create(new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, new LinkReferenceDefinitions())); + var context = new InlineParserContextImpl( + inlineContentParserFactories, delimiterProcessors, bracketProcessors, new LinkReferenceDefinitions()); + this.inlineParserFactory.create(context); } /** @@ -99,7 +104,8 @@ public Node parseReader(Reader input) throws IOException { } private DocumentParser createDocumentParser() { - return new DocumentParser(blockParserFactories, inlineParserFactory, inlineContentParserFactories, delimiterProcessors, includeSourceSpans); + return new DocumentParser(blockParserFactories, inlineParserFactory, inlineContentParserFactories, + delimiterProcessors, bracketProcessors, includeSourceSpans); } private Node postProcess(Node document) { @@ -116,6 +122,7 @@ public static class Builder { private final List blockParserFactories = new ArrayList<>(); private final List inlineContentParserFactories = new ArrayList<>(); private final List delimiterProcessors = new ArrayList<>(); + private final List bracketProcessors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); private InlineParserFactory inlineParserFactory; @@ -240,6 +247,15 @@ public Builder customDelimiterProcessor(DelimiterProcessor delimiterProcessor) { return this; } + /** + * TODO + */ + public Builder bracketProcessor(BracketProcessor bracketProcessor) { + Objects.requireNonNull(bracketProcessor, "bracketProcessor must not be null"); + bracketProcessors.add(bracketProcessor); + return this; + } + public Builder postProcessor(PostProcessor postProcessor) { Objects.requireNonNull(postProcessor, "postProcessor must not be null"); postProcessors.add(postProcessor); diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java b/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java index 7dc49ea50..10002c91b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java @@ -19,8 +19,14 @@ enum ReferenceType { ReferenceType referenceType(); + /** + * The text between the first brackets, e.g. `foo` in `[foo][bar]`. + */ String text(); + /** + * The label, or null for shortcut links (in which case {@link #text()} should be used as the label). + */ String label(); Position afterTextBracket(); diff --git a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java index 26a7da559..c9aa50dc2 100644 --- a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java +++ b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java @@ -1,6 +1,7 @@ package org.commonmark.test; import org.commonmark.internal.InlineParserImpl; +import org.commonmark.parser.beta.BracketProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParser; @@ -51,6 +52,11 @@ public List getCustomDelimiterProcessors() { return inlineParserContext.getCustomDelimiterProcessors(); } + @Override + public List getCustomBracketProcessors() { + return inlineParserContext.getCustomBracketProcessors(); + } + @Override public LinkReferenceDefinition getLinkReferenceDefinition(String label) { lookups.add(label); From f30c787f9bb81b5f324c4519fa121309ebf27792 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 18 May 2024 16:01:38 +1000 Subject: [PATCH 110/345] Actually parse child nodes of footnote definition --- .../internal/FootnoteBlockParser.java | 34 ++++++++++----- .../ext/footnotes/FootnotesTest.java | 42 +++++++++++++++---- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java index 99f75cc4c..a0c1ea181 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java @@ -18,16 +18,26 @@ public Block getBlock() { } @Override - public boolean canHaveLazyContinuationLines() { + public boolean isContainer() { + return true; + } + + @Override + public boolean canContain(Block childBlock) { return true; } @Override public BlockContinue tryContinue(ParserState parserState) { - // We're not continuing to give other block parsers a chance to interrupt this definition. - // But if no other block parser applied (including another FootnotesBlockParser), we will - // accept the line via lazy continuation. - return BlockContinue.none(); + if (parserState.getIndent() >= 4) { + // It looks like content needs to be indented by 4 so that it's part of a footnote (instead of starting a new block). + return BlockContinue.atColumn(4); + } else { + // We're not continuing to give other block parsers a chance to interrupt this definition. + // But if no other block parser applied (including another FootnotesBlockParser), we will + // accept the line via lazy continuation (same as a block quote). + return BlockContinue.none(); + } } public static class Factory implements BlockParserFactory { @@ -47,12 +57,14 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar // Now at first label character (if any) index++; - for (int i = index; i < content.length(); i++) { - var c = content.charAt(i); - if (c == ']') { - if (i > index) { - var label = content.subSequence(index, i).toString(); - return BlockStart.of(new FootnoteBlockParser(label)); + var labelStart = index; + + for (index = labelStart; index < content.length(); index++) { + var c = content.charAt(index); + if (c == ']' && index + 1 < content.length() && content.charAt(index + 1) == ':') { + if (index > labelStart) { + var label = content.subSequence(labelStart, index).toString(); + return BlockStart.of(new FootnoteBlockParser(label)).atIndex(index + 2); } else { return BlockStart.none(); } diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index 92e51b6da..d9e44b5c4 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -19,7 +19,7 @@ public class FootnotesTest { private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); @Test - public void testBlockStart() { + public void testDefBlockStart() { for (var s : List.of("1", "a")) { var doc = PARSER.parse("[^" + s + "]: footnote\n"); var def = find(doc, FootnoteDefinition.class); @@ -37,7 +37,8 @@ public void testBlockStart() { } @Test - public void testBlockStartInterrupts() { + public void testDefBlockStartInterrupts() { + // This is different from a link reference definition, which can only be at the start of paragraphs. var doc = PARSER.parse("test\n[^1]: footnote\n"); var paragraph = find(doc, Paragraph.class); var def = find(doc, FootnoteDefinition.class); @@ -46,7 +47,7 @@ public void testBlockStartInterrupts() { } @Test - public void testMultiple() { + public void testDefMultiple() { var doc = PARSER.parse("[^1]: foo\n[^2]: bar\n"); var defs = findAll(doc, FootnoteDefinition.class); assertEquals("1", defs.get(0).getLabel()); @@ -54,7 +55,7 @@ public void testMultiple() { } @Test - public void testBlockStartAfterLinkReferenceDefinition() { + public void testDefBlockStartAfterLinkReferenceDefinition() { var doc = PARSER.parse("[foo]: /url\n[^1]: footnote\n"); var linkReferenceDef = find(doc, LinkReferenceDefinition.class); var footnotesDef = find(doc, FootnoteDefinition.class); @@ -63,15 +64,42 @@ public void testBlockStartAfterLinkReferenceDefinition() { } @Test - public void testBlockContinue() { + public void testDefContainsParagraph() { + var doc = PARSER.parse("[^1]: footnote\n"); + var def = find(doc, FootnoteDefinition.class); + var paragraph = (Paragraph) def.getFirstChild(); + var text = (Text) paragraph.getFirstChild(); + assertEquals("footnote", text.getLiteral()); + } + + @Test + public void testDefContainsMultipleLines() { var doc = PARSER.parse("[^1]: footnote\nstill\n"); var def = find(doc, FootnoteDefinition.class); assertEquals("1", def.getLabel()); - assertNull(tryFind(doc, Paragraph.class)); + var paragraph = (Paragraph) def.getFirstChild(); + var text1 = (Text) paragraph.getFirstChild(); + var text2 = (Text) paragraph.getLastChild(); + assertEquals("footnote", text1.getLiteral()); + assertEquals("still", text2.getLiteral()); + } + + @Test + public void testDefContainsList() { + var doc = PARSER.parse("[^1]: - foo\n - bar\n"); + var def = find(doc, FootnoteDefinition.class); + assertEquals("1", def.getLabel()); + var list = (BulletList) def.getFirstChild(); + var item1 = (ListItem) list.getFirstChild(); + var item2 = (ListItem) list.getLastChild(); + var text1 = (Text) item1.getFirstChild().getFirstChild(); + var text2 = (Text) item2.getFirstChild().getFirstChild(); + assertEquals("foo", text1.getLiteral()); + assertEquals("bar", text2.getLiteral()); } @Test - public void testFootnotesDefinitionInterruptedByOthers() { + public void testDefInterruptedByOthers() { var doc = PARSER.parse("[^1]: footnote\n# Heading\n"); var def = find(doc, FootnoteDefinition.class); var heading = find(doc, Heading.class); From 04ba63f024ab5dbf8ef9e82b9fee4993d60780fe Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 19 May 2024 22:13:20 +1000 Subject: [PATCH 111/345] Extract DefinitionMap as a public class --- .../commonmark/internal/DocumentParser.java | 14 +++++-- .../internal/InlineParserContextImpl.java | 5 ++- .../internal/LinkReferenceDefinitions.java | 27 ------------- .../org/commonmark/node/DefinitionMap.java | 38 +++++++++++++++++++ .../java/org/commonmark/parser/Parser.java | 3 +- 5 files changed, 52 insertions(+), 35 deletions(-) delete mode 100644 commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitions.java create mode 100644 commonmark/src/main/java/org/commonmark/node/DefinitionMap.java diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index e3be3cfa3..af0cb16b7 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -2,7 +2,10 @@ import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; -import org.commonmark.parser.*; +import org.commonmark.parser.IncludeSourceSpans; +import org.commonmark.parser.InlineParserFactory; +import org.commonmark.parser.SourceLine; +import org.commonmark.parser.SourceLines; import org.commonmark.parser.beta.BracketProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.block.*; @@ -73,7 +76,7 @@ public class DocumentParser implements ParserState { private final List bracketProcessors; private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; - private final LinkReferenceDefinitions definitions = new LinkReferenceDefinitions(); + private final DefinitionMap linkReferenceDefinitions = new DefinitionMap<>(); private final List openBlockParsers = new ArrayList<>(); private final List allBlockParsers = new ArrayList<>(); @@ -472,11 +475,14 @@ private void finalize(BlockParser blockParser) { } private void addDefinitionsFrom(ParagraphParser paragraphParser) { + // TODO: Generalize this allow block parsers to add definitions by their types. + // We'll keep a map for each type, e.g. one for LinkReferenceDefinition, one for FootnoteDefinition, etc :) + // The context then allows lookup with the type and label for (LinkReferenceDefinition definition : paragraphParser.getDefinitions()) { // Add nodes into document before paragraph. paragraphParser.getBlock().insertBefore(definition); - definitions.add(definition); + linkReferenceDefinitions.putIfAbsent(definition.getLabel(), definition); } } @@ -484,7 +490,7 @@ private void addDefinitionsFrom(ParagraphParser paragraphParser) { * Walk through a block & children recursively, parsing string content into inline content where appropriate. */ private void processInlines() { - var context = new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, bracketProcessors, definitions); + var context = new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, bracketProcessors, linkReferenceDefinitions); var inlineParser = inlineParserFactory.create(context); for (var blockParser : allBlockParsers) { diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java index d7bf8d16b..ef99c0c9c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java @@ -1,5 +1,6 @@ package org.commonmark.internal; +import org.commonmark.node.DefinitionMap; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.beta.BracketProcessor; @@ -13,12 +14,12 @@ public class InlineParserContextImpl implements InlineParserContext { private final List inlineContentParserFactories; private final List delimiterProcessors; private final List bracketProcessors; - private final LinkReferenceDefinitions linkReferenceDefinitions; + private final DefinitionMap linkReferenceDefinitions; public InlineParserContextImpl(List inlineContentParserFactories, List delimiterProcessors, List bracketProcessors, - LinkReferenceDefinitions linkReferenceDefinitions) { + DefinitionMap linkReferenceDefinitions) { this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; this.bracketProcessors = bracketProcessors; diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitions.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitions.java deleted file mode 100644 index 8fbdb982a..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitions.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.commonmark.internal; - -import org.commonmark.internal.util.Escaping; -import org.commonmark.node.LinkReferenceDefinition; - -import java.util.LinkedHashMap; -import java.util.Map; - -public class LinkReferenceDefinitions { - - // LinkedHashMap for determinism and to preserve document order - private final Map definitions = new LinkedHashMap<>(); - - public void add(LinkReferenceDefinition definition) { - String normalizedLabel = Escaping.normalizeLabelContent(definition.getLabel()); - - // spec: When there are multiple matching link reference definitions, the first is used - if (!definitions.containsKey(normalizedLabel)) { - definitions.put(normalizedLabel, definition); - } - } - - public LinkReferenceDefinition get(String label) { - String normalizedLabel = Escaping.normalizeLabelContent(label); - return definitions.get(normalizedLabel); - } -} diff --git a/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java b/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java new file mode 100644 index 000000000..bc0c0b221 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java @@ -0,0 +1,38 @@ +package org.commonmark.node; + +import org.commonmark.internal.util.Escaping; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A map that can be used to store and lookup reference definitions by a label. The labels are case-insensitive and + * normalized, the same way as for {@link LinkReferenceDefinition} nodes. + * + * @param the type of value + */ +public class DefinitionMap { + + // LinkedHashMap for determinism and to preserve document order + private final Map definitions = new LinkedHashMap<>(); + + /** + * Store a new definition unless one is already in the map. + */ + public void putIfAbsent(String label, V definition) { + String normalizedLabel = Escaping.normalizeLabelContent(label); + + // spec: When there are multiple matching link reference definitions, the first is used + definitions.putIfAbsent(normalizedLabel, definition); + } + + /** + * Lookup a definition by normalized label. + * + * @return the value or null + */ + public V get(String label) { + String normalizedLabel = Escaping.normalizeLabelContent(label); + return definitions.get(normalizedLabel); + } +} diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index dc91f5dc3..6a626b2bc 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -4,7 +4,6 @@ import org.commonmark.internal.DocumentParser; import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; -import org.commonmark.internal.LinkReferenceDefinitions; import org.commonmark.node.*; import org.commonmark.parser.beta.BracketProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; @@ -50,7 +49,7 @@ private Parser(Builder builder) { // Try to construct an inline parser. Invalid configuration might result in an exception, which we want to // detect as soon as possible. var context = new InlineParserContextImpl( - inlineContentParserFactories, delimiterProcessors, bracketProcessors, new LinkReferenceDefinitions()); + inlineContentParserFactories, delimiterProcessors, bracketProcessors, new DefinitionMap<>()); this.inlineParserFactory.create(context); } From 4c2f72948f261fd52c81b20b34145f9740362156 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 25 May 2024 22:39:51 +1000 Subject: [PATCH 112/345] Allow BlockParsers to return definitions (for lookup during inline parsing) --- .../internal/FootnoteBlockParser.java | 10 +++++ .../internal/FootnoteBracketProcessor.java | 14 +++--- .../ext/footnotes/FootnotesTest.java | 6 +++ .../org/commonmark/internal/Definitions.java | 33 ++++++++++++++ .../commonmark/internal/DocumentParser.java | 45 ++++++++----------- .../internal/InlineParserContextImpl.java | 14 +++--- .../LinkReferenceDefinitionParser.java | 1 + .../commonmark/internal/ParagraphParser.java | 23 ++++++---- .../internal/inline/CoreBracketProcessor.java | 3 +- .../org/commonmark/node/DefinitionMap.java | 30 ++++++++++--- .../parser/InlineParserContext.java | 15 ++++++- .../java/org/commonmark/parser/Parser.java | 3 +- .../parser/block/AbstractBlockParser.java | 8 ++++ .../commonmark/parser/block/BlockParser.java | 9 ++++ .../test/InlineParserContextTest.java | 7 ++- 15 files changed, 166 insertions(+), 55 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/Definitions.java diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java index a0c1ea181..55ca86993 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java @@ -2,8 +2,11 @@ import org.commonmark.ext.footnotes.FootnoteDefinition; import org.commonmark.node.Block; +import org.commonmark.node.DefinitionMap; import org.commonmark.parser.block.*; +import java.util.List; + public class FootnoteBlockParser extends AbstractBlockParser { private final FootnoteDefinition block; @@ -40,6 +43,13 @@ public BlockContinue tryContinue(ParserState parserState) { } } + @Override + public List> getDefinitions() { + var map = new DefinitionMap<>(FootnoteDefinition.class); + map.putIfAbsent(block.getLabel(), block); + return List.of(map); + } + public static class Factory implements BlockParserFactory { @Override diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java index 6fa4ac3ee..75f98b0bf 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java @@ -1,5 +1,6 @@ package org.commonmark.ext.footnotes.internal; +import org.commonmark.ext.footnotes.FootnoteDefinition; import org.commonmark.ext.footnotes.FootnoteReference; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.beta.BracketInfo; @@ -13,13 +14,14 @@ public BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlinePar // TODO: Does parsing need to be more strict here? var text = bracketInfo.text(); if (text.startsWith("^")) { - // TODO: Do we need to check if a definition exists before doing this? (That would be the same as reference - // links.) - - // For footnotes, we only ever consume the text part of the link, not the label part (if any). - var position = bracketInfo.afterTextBracket(); var label = text.substring(1); - return BracketResult.replaceWith(new FootnoteReference(label), position); + // Check if we have a definition, otherwise ignore (same behavior as for link reference definitions) + var def = context.getDefinition(FootnoteDefinition.class, label); + if (def != null) { + // For footnotes, we only ever consume the text part of the link, not the label part (if any). + var position = bracketInfo.afterTextBracket(); + return BracketResult.replaceWith(new FootnoteReference(label), position); + } } return BracketResult.none(); } diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index d9e44b5c4..51b722701 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -114,6 +114,12 @@ public void testReference() { assertEquals("foo", ref.getLabel()); } + @Test + public void testReferenceNoDefinition() { + var doc = PARSER.parse("Test [^foo]\n"); + assertNull(tryFind(doc, FootnoteReference.class)); + } + // Interesting test cases: // Test [foo][^bar] diff --git a/commonmark/src/main/java/org/commonmark/internal/Definitions.java b/commonmark/src/main/java/org/commonmark/internal/Definitions.java new file mode 100644 index 000000000..0377842c9 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/Definitions.java @@ -0,0 +1,33 @@ +package org.commonmark.internal; + +import org.commonmark.node.DefinitionMap; + +import java.util.HashMap; +import java.util.Map; + +public class Definitions { + + private final Map, DefinitionMap> definitionsByType = new HashMap<>(); + + public void addDefinitions(DefinitionMap definitionMap) { + var existingMap = getMap(definitionMap.getType()); + if (existingMap == null) { + definitionsByType.put(definitionMap.getType(), definitionMap); + } else { + existingMap.addAll(definitionMap); + } + } + + public V getDefinition(Class type, String label) { + var definitionMap = getMap(type); + if (definitionMap == null) { + return null; + } + return definitionMap.get(label); + } + + private DefinitionMap getMap(Class type) { + //noinspection unchecked + return (DefinitionMap) definitionsByType.get(type); + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index af0cb16b7..cff40486b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -76,7 +76,7 @@ public class DocumentParser implements ParserState { private final List bracketProcessors; private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; - private final DefinitionMap linkReferenceDefinitions = new DefinitionMap<>(); + private final Definitions definitions = new Definitions(); private final List openBlockParsers = new ArrayList<>(); private final List allBlockParsers = new ArrayList<>(); @@ -462,35 +462,11 @@ private BlockStartImpl findBlockStart(BlockParser blockParser) { return null; } - /** - * Finalize a block. Close it and do any necessary postprocessing, e.g. setting the content of blocks and - * collecting link reference definitions from paragraphs. - */ - private void finalize(BlockParser blockParser) { - if (blockParser instanceof ParagraphParser) { - addDefinitionsFrom((ParagraphParser) blockParser); - } - - blockParser.closeBlock(); - } - - private void addDefinitionsFrom(ParagraphParser paragraphParser) { - // TODO: Generalize this allow block parsers to add definitions by their types. - // We'll keep a map for each type, e.g. one for LinkReferenceDefinition, one for FootnoteDefinition, etc :) - // The context then allows lookup with the type and label - for (LinkReferenceDefinition definition : paragraphParser.getDefinitions()) { - // Add nodes into document before paragraph. - paragraphParser.getBlock().insertBefore(definition); - - linkReferenceDefinitions.putIfAbsent(definition.getLabel(), definition); - } - } - /** * Walk through a block & children recursively, parsing string content into inline content where appropriate. */ private void processInlines() { - var context = new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, bracketProcessors, linkReferenceDefinitions); + var context = new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, bracketProcessors, definitions); var inlineParser = inlineParserFactory.create(context); for (var blockParser : allBlockParsers) { @@ -529,7 +505,7 @@ private Block prepareActiveBlockParserForReplacement() { // block parser got the current paragraph content using MatchedBlockParser#getContentString. In case the // paragraph started with link reference definitions, we parse and strip them before the block parser gets // the content. We want to keep them. - // If no replacement happens, we collect the definitions as part of finalizing paragraph blocks. + // If no replacement happens, we collect the definitions as part of finalizing blocks. addDefinitionsFrom(paragraphParser); } @@ -556,6 +532,21 @@ private void closeBlockParsers(int count) { } } + /** + * Finalize a block. Close it and do any necessary postprocessing, e.g. setting the content of blocks and + * collecting link reference definitions from paragraphs. + */ + private void finalize(BlockParser blockParser) { + addDefinitionsFrom(blockParser); + blockParser.closeBlock(); + } + + private void addDefinitionsFrom(BlockParser blockParser) { + for (var definitionMap : blockParser.getDefinitions()) { + definitions.addDefinitions(definitionMap); + } + } + /** * Prepares the input line replacing {@code \0} */ diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java index ef99c0c9c..79fe2a56a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java @@ -1,6 +1,5 @@ package org.commonmark.internal; -import org.commonmark.node.DefinitionMap; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.beta.BracketProcessor; @@ -14,16 +13,16 @@ public class InlineParserContextImpl implements InlineParserContext { private final List inlineContentParserFactories; private final List delimiterProcessors; private final List bracketProcessors; - private final DefinitionMap linkReferenceDefinitions; + private final Definitions definitions; public InlineParserContextImpl(List inlineContentParserFactories, List delimiterProcessors, List bracketProcessors, - DefinitionMap linkReferenceDefinitions) { + Definitions definitions) { this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; this.bracketProcessors = bracketProcessors; - this.linkReferenceDefinitions = linkReferenceDefinitions; + this.definitions = definitions; } @Override @@ -43,6 +42,11 @@ public List getCustomBracketProcessors() { @Override public LinkReferenceDefinition getLinkReferenceDefinition(String label) { - return linkReferenceDefinitions.get(label); + return definitions.getDefinition(LinkReferenceDefinition.class, label); + } + + @Override + public D getDefinition(Class type, String label) { + return definitions.getDefinition(type, label); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index b58e669ef..070f29ceb 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -2,6 +2,7 @@ import org.commonmark.internal.util.Escaping; import org.commonmark.internal.util.LinkScanner; +import org.commonmark.node.DefinitionMap; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.node.SourceSpan; import org.commonmark.parser.SourceLine; diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index 89328ef2a..18808d499 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -1,9 +1,6 @@ package org.commonmark.internal; -import org.commonmark.node.Block; -import org.commonmark.node.LinkReferenceDefinition; -import org.commonmark.node.Paragraph; -import org.commonmark.node.SourceSpan; +import org.commonmark.node.*; import org.commonmark.parser.InlineParser; import org.commonmark.parser.SourceLine; import org.commonmark.parser.SourceLines; @@ -12,6 +9,7 @@ import org.commonmark.parser.block.ParserState; import java.util.List; +import java.util.Map; public class ParagraphParser extends AbstractBlockParser { @@ -49,8 +47,21 @@ public void addSourceSpan(SourceSpan sourceSpan) { linkReferenceDefinitionParser.addSourceSpan(sourceSpan); } + @Override + public List> getDefinitions() { + var map = new DefinitionMap<>(LinkReferenceDefinition.class); + for (var def : linkReferenceDefinitionParser.getDefinitions()) { + map.putIfAbsent(def.getLabel(), def); + } + return List.of(map); + } + @Override public void closeBlock() { + for (var def : linkReferenceDefinitionParser.getDefinitions()) { + block.insertBefore(def); + } + if (linkReferenceDefinitionParser.getParagraphLines().isEmpty()) { block.unlink(); } else { @@ -69,8 +80,4 @@ public void parseInlines(InlineParser inlineParser) { public SourceLines getParagraphLines() { return linkReferenceDefinitionParser.getParagraphLines(); } - - public List getDefinitions() { - return linkReferenceDefinitionParser.getDefinitions(); - } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java index f208f76e2..ce5fc27af 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java @@ -2,6 +2,7 @@ import org.commonmark.node.Image; import org.commonmark.node.Link; +import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.beta.BracketInfo; import org.commonmark.parser.beta.BracketProcessor; @@ -14,7 +15,7 @@ public class CoreBracketProcessor implements BracketProcessor { public BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlineParserContext context) { var label = bracketInfo.label(); var ref = label != null && !label.isEmpty() ? label : bracketInfo.text(); - var def = context.getLinkReferenceDefinition(ref); + var def = context.getDefinition(LinkReferenceDefinition.class, ref); if (def != null) { if (bracketInfo.openerType() == BracketInfo.OpenerType.IMAGE) { return BracketResult.wrapTextIn(new Image(def.getDestination(), def.getTitle()), scanner.position()); diff --git a/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java b/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java index bc0c0b221..82f553ff1 100644 --- a/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java +++ b/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java @@ -2,6 +2,7 @@ import org.commonmark.internal.util.Escaping; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -9,17 +10,32 @@ * A map that can be used to store and lookup reference definitions by a label. The labels are case-insensitive and * normalized, the same way as for {@link LinkReferenceDefinition} nodes. * - * @param the type of value + * @param the type of value */ -public class DefinitionMap { +public class DefinitionMap { + private final Class type; // LinkedHashMap for determinism and to preserve document order - private final Map definitions = new LinkedHashMap<>(); + private final Map definitions = new LinkedHashMap<>(); + + public DefinitionMap(Class type) { + this.type = type; + } + + public Class getType() { + return type; + } + + public void addAll(DefinitionMap that) { + for (var entry : that.definitions.entrySet()) { + definitions.putIfAbsent(entry.getKey(), entry.getValue()); + } + } /** * Store a new definition unless one is already in the map. */ - public void putIfAbsent(String label, V definition) { + public void putIfAbsent(String label, D definition) { String normalizedLabel = Escaping.normalizeLabelContent(label); // spec: When there are multiple matching link reference definitions, the first is used @@ -31,8 +47,12 @@ public void putIfAbsent(String label, V definition) { * * @return the value or null */ - public V get(String label) { + public D get(String label) { String normalizedLabel = Escaping.normalizeLabelContent(label); return definitions.get(normalizedLabel); } + + public Collection values() { + return definitions.values(); + } } diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java index 6c3c9cbc6..4a2951e70 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -32,10 +32,23 @@ public interface InlineParserContext { /** * Look up a {@link LinkReferenceDefinition} for a given label. *

      - * Note that the label is not normalized yet; implementations are responsible for normalizing before lookup. + * Note that the passed in label does not need to be normalized; implementations are responsible for doing the + * normalization before lookup. * * @param label the link label to look up * @return the definition if one exists, {@code null} otherwise + * @deprecated use {@link #getDefinition} with {@link LinkReferenceDefinition} instead */ + @Deprecated LinkReferenceDefinition getLinkReferenceDefinition(String label); + + /** + * Look up a definition of a type for a given label. + *

      + * Note that the passed in label does not need to be normalized; implementations are responsible for doing the + * normalization before lookup. + * + * @return the definition if one exists, null otherwise + */ + D getDefinition(Class type, String label); } diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 6a626b2bc..e09d45da3 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -1,6 +1,7 @@ package org.commonmark.parser; import org.commonmark.Extension; +import org.commonmark.internal.Definitions; import org.commonmark.internal.DocumentParser; import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; @@ -49,7 +50,7 @@ private Parser(Builder builder) { // Try to construct an inline parser. Invalid configuration might result in an exception, which we want to // detect as soon as possible. var context = new InlineParserContextImpl( - inlineContentParserFactories, delimiterProcessors, bracketProcessors, new DefinitionMap<>()); + inlineContentParserFactories, delimiterProcessors, bracketProcessors, new Definitions()); this.inlineParserFactory.create(context); } diff --git a/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java b/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java index 3d4cbb77b..4fb1a05ac 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java @@ -1,10 +1,13 @@ package org.commonmark.parser.block; import org.commonmark.node.Block; +import org.commonmark.node.DefinitionMap; import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; import org.commonmark.parser.SourceLine; +import java.util.List; + public abstract class AbstractBlockParser implements BlockParser { @Override @@ -31,6 +34,11 @@ public void addSourceSpan(SourceSpan sourceSpan) { getBlock().addSourceSpan(sourceSpan); } + @Override + public List> getDefinitions() { + return List.of(); + } + @Override public void closeBlock() { } diff --git a/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java b/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java index addd90d1a..32ff2a474 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java @@ -1,10 +1,13 @@ package org.commonmark.parser.block; import org.commonmark.node.Block; +import org.commonmark.node.DefinitionMap; import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; import org.commonmark.parser.SourceLine; +import java.util.List; + /** * Parser for a specific block node. *

      @@ -49,6 +52,12 @@ public interface BlockParser { */ void addSourceSpan(SourceSpan sourceSpan); + /** + * Return definitions parsed by this parser. The definitions returned here can later be accessed during inline + * parsing via {@link org.commonmark.parser.InlineParserContext#getDefinition}. + */ + List> getDefinitions(); + void closeBlock(); void parseInlines(InlineParser inlineParser); diff --git a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java index c9aa50dc2..07b94c076 100644 --- a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java +++ b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java @@ -59,8 +59,13 @@ public List getCustomBracketProcessors() { @Override public LinkReferenceDefinition getLinkReferenceDefinition(String label) { + return getDefinition(LinkReferenceDefinition.class, label); + } + + @Override + public D getDefinition(Class type, String label) { lookups.add(label); - return inlineParserContext.getLinkReferenceDefinition(label); + return inlineParserContext.getDefinition(type, label); } }; From 016eea816361925df9713be685c6b95f51ab70f7 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 26 May 2024 22:02:24 +1000 Subject: [PATCH 113/345] Fix replace mode --- .../org/commonmark/ext/footnotes/FootnotesTest.java | 13 +++++++++++++ .../org/commonmark/internal/InlineParserImpl.java | 5 ++++- .../org/commonmark/parser/beta/BracketResult.java | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index 51b722701..e8a6db6dc 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -120,6 +120,19 @@ public void testReferenceNoDefinition() { assertNull(tryFind(doc, FootnoteReference.class)); } + @Test + public void testRefWithEmphasis() { + var doc = PARSER.parse("Test [^*foo*]\n\n[^*foo*]: def"); + var ref = find(doc, FootnoteReference.class); + assertEquals("*foo*", ref.getLabel()); + assertNull(ref.getFirstChild()); + var paragraph = doc.getFirstChild(); + var text = (Text) paragraph.getFirstChild(); + assertEquals("Test ", text.getLiteral()); + assertEquals(ref, text.getNext()); + assertEquals(ref, paragraph.getLastChild()); + } + // Interesting test cases: // Test [foo][^bar] diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 6e4ad9c61..f3a68a947 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -426,8 +426,11 @@ public Position afterTextBracket() { // TODO: startFromBracket needs to split the opening node.. Maybe we should just keep ! and [ // as separate nodes in Bracket - for (Node n = opener.node; n != null; n = n.getNext()) { + Node n = opener.node; + while (n != null) { + var next = n.getNext(); n.unlink(); + n = next; } return node; } diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/BracketResult.java b/commonmark/src/main/java/org/commonmark/parser/beta/BracketResult.java index 6a3a23cef..e9571391b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/BracketResult.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/BracketResult.java @@ -13,7 +13,7 @@ static BracketResult wrapTextIn(Node node, Position position) { } static BracketResult replaceWith(Node node, Position position) { - return new BracketResultImpl(BracketResultImpl.Type.WRAP, node, position); + return new BracketResultImpl(BracketResultImpl.Type.REPLACE, node, position); } BracketResult startFromBracket(); From 0606f051f0155d503e8a6535d5c41756ffb050a8 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 1 Jun 2024 11:44:35 +1000 Subject: [PATCH 114/345] Implement startFromBracket --- .../internal/FootnoteBracketProcessor.java | 3 +- .../ext/footnotes/FootnotesTest.java | 12 +++++- .../java/org/commonmark/internal/Bracket.java | 35 ++++++++++++----- .../commonmark/internal/InlineParserImpl.java | 39 ++++++++++--------- .../internal/inline/BracketResultImpl.java | 1 - 5 files changed, 59 insertions(+), 31 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java index 75f98b0bf..d2fa434ac 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java @@ -20,7 +20,8 @@ public BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlinePar if (def != null) { // For footnotes, we only ever consume the text part of the link, not the label part (if any). var position = bracketInfo.afterTextBracket(); - return BracketResult.replaceWith(new FootnoteReference(label), position); + // If the marker is `![`, we don't want to include the `!`, so start from bracket + return BracketResult.replaceWith(new FootnoteReference(label), position).startFromBracket(); } } return BracketResult.none(); diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index e8a6db6dc..fbe5b3c69 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -122,7 +122,7 @@ public void testReferenceNoDefinition() { @Test public void testRefWithEmphasis() { - var doc = PARSER.parse("Test [^*foo*]\n\n[^*foo*]: def"); + var doc = PARSER.parse("Test [^*foo*]\n\n[^*foo*]: def\n"); var ref = find(doc, FootnoteReference.class); assertEquals("*foo*", ref.getLabel()); assertNull(ref.getFirstChild()); @@ -133,6 +133,16 @@ public void testRefWithEmphasis() { assertEquals(ref, paragraph.getLastChild()); } + @Test + public void testRefAfterBang() { + var doc = PARSER.parse("Test![^foo]\n\n[^foo]: def\n"); + var ref = find(doc, FootnoteReference.class); + assertEquals("foo", ref.getLabel()); + var paragraph = doc.getFirstChild(); + var text = (Text) paragraph.getFirstChild(); + assertEquals("Test!", text.getLiteral()); + } + // Interesting test cases: // Test [foo][^bar] diff --git a/commonmark/src/main/java/org/commonmark/internal/Bracket.java b/commonmark/src/main/java/org/commonmark/internal/Bracket.java index 2a91b877e..c2e14f4f1 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Bracket.java +++ b/commonmark/src/main/java/org/commonmark/internal/Bracket.java @@ -8,12 +8,25 @@ */ public class Bracket { - public final Text node; + /** + * The node of {@code !} if present, null otherwise. + */ + public final Text bangNode; + + /** + * The position of {@code !} if present, null otherwise. + */ + public final Position bangPosition; + + /** + * The node of {@code [}. + */ + public final Text bracketNode; /** - * The position of the marker for the bracket ([ or ![) + * The position of {@code [}. */ - public final Position markerPosition; + public final Position bracketPosition; /** * The position of the content (after the opening bracket) @@ -45,17 +58,19 @@ public class Bracket { */ public boolean bracketAfter = false; - static public Bracket link(Text node, Position markerPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { - return new Bracket(node, markerPosition, contentPosition, previous, previousDelimiter, false); + static public Bracket link(Text bracketNode, Position bracketPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { + return new Bracket(null, null, bracketNode, bracketPosition, contentPosition, previous, previousDelimiter, false); } - static public Bracket image(Text node, Position markerPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { - return new Bracket(node, markerPosition, contentPosition, previous, previousDelimiter, true); + static public Bracket image(Text bangNode, Position bangPosition, Text bracketNode, Position bracketPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { + return new Bracket(bangNode, bangPosition, bracketNode, bracketPosition, contentPosition, previous, previousDelimiter, true); } - private Bracket(Text node, Position markerPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter, boolean image) { - this.node = node; - this.markerPosition = markerPosition; + private Bracket(Text bangNode, Position bangPosition, Text bracketNode, Position bracketPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter, boolean image) { + this.bangNode = bangNode; + this.bangPosition = bangPosition; + this.bracketNode = bracketNode; + this.bracketPosition = bracketPosition; this.contentPosition = contentPosition; this.image = image; this.previous = previous; diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index f3a68a947..cf77f94aa 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -186,7 +186,7 @@ private List parseInline() { case '[': return List.of(parseOpenBracket()); case '!': - return List.of(parseBang()); + return parseBang(); case ']': return List.of(parseCloseBracket()); case '\n': @@ -272,18 +272,20 @@ private Node parseOpenBracket() { * If next character is [, and ! delimiter to delimiter stack and add a text node to block's children. * Otherwise just add a text node. */ - private Node parseBang() { - Position start = scanner.position(); + private List parseBang() { + var bangPosition = scanner.position(); scanner.next(); + var bracketPosition = scanner.position(); if (scanner.next('[')) { - Position contentPosition = scanner.position(); - Text node = text(scanner.getSource(start, contentPosition)); + var contentPosition = scanner.position(); + var bangNode = text(scanner.getSource(bangPosition, bracketPosition)); + var bracketNode = text(scanner.getSource(bracketPosition, contentPosition)); // Add entry to stack for this opener - addBracket(Bracket.image(node, start, contentPosition, lastBracket, lastDelimiter)); - return node; + addBracket(Bracket.image(bangNode, bangPosition, bracketNode, bracketPosition, contentPosition, lastBracket, lastDelimiter)); + return List.of(bangNode, bracketNode); } else { - return text(scanner.getSource(start, scanner.position())); + return List.of(text(scanner.getSource(bangPosition, scanner.position()))); } } @@ -337,7 +339,7 @@ private Node parseLinkOrImage(Bracket opener, Position beforeClose) { var linkOrImage = opener.image ? new Image(destinationTitle.destination, destinationTitle.title) : new Link(destinationTitle.destination, destinationTitle.title); - return processLinkOrImage(opener, linkOrImage); + return processLinkOrImage(opener, linkOrImage, false); } // Not an inline link/image, rewind back to after `]`. scanner.setPosition(afterClose); @@ -412,8 +414,7 @@ public Position afterTextBracket() { switch (result.getType()) { case WRAP: scanner.setPosition(position); - // TODO: startFromBracket - return processLinkOrImage(opener, node); + return processLinkOrImage(opener, node, startFromBracket); case REPLACE: scanner.setPosition(position); @@ -424,9 +425,7 @@ public Position afterTextBracket() { removeLastBracket(); - // TODO: startFromBracket needs to split the opening node.. Maybe we should just keep ! and [ - // as separate nodes in Bracket - Node n = opener.node; + Node n = opener.bangNode == null || startFromBracket ? opener.bracketNode : opener.bangNode; while (n != null) { var next = n.getNext(); n.unlink(); @@ -439,9 +438,9 @@ public Position afterTextBracket() { return null; } - private Node processLinkOrImage(Bracket opener, Node linkOrImage) { + private Node processLinkOrImage(Bracket opener, Node linkOrImage, boolean startFromBracket) { // Add all nodes between the opening bracket and now (closing bracket) as child nodes of the link - Node node = opener.node.getNext(); + Node node = opener.bracketNode.getNext(); while (node != null) { Node next = node.getNext(); linkOrImage.appendChild(node); @@ -449,14 +448,18 @@ private Node processLinkOrImage(Bracket opener, Node linkOrImage) { } if (includeSourceSpans) { - linkOrImage.setSourceSpans(scanner.getSource(opener.markerPosition, scanner.position()).getSourceSpans()); + var startPosition = opener.bangPosition == null || startFromBracket ? opener.bracketPosition : opener.bangPosition; + linkOrImage.setSourceSpans(scanner.getSource(startPosition, scanner.position()).getSourceSpans()); } // Process delimiters such as emphasis inside link/image processDelimiters(opener.previousDelimiter); mergeChildTextNodes(linkOrImage); // We don't need the corresponding text node anymore, we turned it into a link/image node - opener.node.unlink(); + if (opener.bangNode != null && !startFromBracket) { + opener.bangNode.unlink(); + } + opener.bracketNode.unlink(); removeLastBracket(); // Links within links are not allowed. We found this link, so there can be no other link around it. diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BracketResultImpl.java b/commonmark/src/main/java/org/commonmark/internal/inline/BracketResultImpl.java index 1ae450c9f..5a0f18515 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BracketResultImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BracketResultImpl.java @@ -1,6 +1,5 @@ package org.commonmark.internal.inline; -import org.commonmark.internal.InlineParserImpl; import org.commonmark.node.Node; import org.commonmark.parser.beta.BracketResult; import org.commonmark.parser.beta.Position; From aa90ab01b81a3c4f9dc10df526be2e919b74a004 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 1 Jun 2024 16:41:35 +1000 Subject: [PATCH 115/345] More test cases --- .../ext/footnotes/FootnotesTest.java | 101 +++++++++--------- 1 file changed, 53 insertions(+), 48 deletions(-) diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index fbe5b3c69..570b39544 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -68,8 +68,7 @@ public void testDefContainsParagraph() { var doc = PARSER.parse("[^1]: footnote\n"); var def = find(doc, FootnoteDefinition.class); var paragraph = (Paragraph) def.getFirstChild(); - var text = (Text) paragraph.getFirstChild(); - assertEquals("footnote", text.getLiteral()); + assertText("footnote", paragraph.getFirstChild()); } @Test @@ -78,10 +77,8 @@ public void testDefContainsMultipleLines() { var def = find(doc, FootnoteDefinition.class); assertEquals("1", def.getLabel()); var paragraph = (Paragraph) def.getFirstChild(); - var text1 = (Text) paragraph.getFirstChild(); - var text2 = (Text) paragraph.getLastChild(); - assertEquals("footnote", text1.getLiteral()); - assertEquals("still", text2.getLiteral()); + assertText("footnote", paragraph.getFirstChild()); + assertText("still", paragraph.getLastChild()); } @Test @@ -92,10 +89,8 @@ public void testDefContainsList() { var list = (BulletList) def.getFirstChild(); var item1 = (ListItem) list.getFirstChild(); var item2 = (ListItem) list.getLastChild(); - var text1 = (Text) item1.getFirstChild().getFirstChild(); - var text2 = (Text) item2.getFirstChild().getFirstChild(); - assertEquals("foo", text1.getLiteral()); - assertEquals("bar", text2.getLiteral()); + assertText("foo", item1.getFirstChild().getFirstChild()); + assertText("bar", item2.getFirstChild().getFirstChild()); } @Test @@ -104,7 +99,7 @@ public void testDefInterruptedByOthers() { var def = find(doc, FootnoteDefinition.class); var heading = find(doc, Heading.class); assertEquals("1", def.getLabel()); - assertEquals("Heading", ((Text) heading.getFirstChild()).getLiteral()); + assertText("Heading", heading.getFirstChild()); } @Test @@ -121,7 +116,8 @@ public void testReferenceNoDefinition() { } @Test - public void testRefWithEmphasis() { + public void testRefWithEmphasisInside() { + // No emphasis inside footnote reference, should just be treated as text var doc = PARSER.parse("Test [^*foo*]\n\n[^*foo*]: def\n"); var ref = find(doc, FootnoteReference.class); assertEquals("*foo*", ref.getLabel()); @@ -133,47 +129,57 @@ public void testRefWithEmphasis() { assertEquals(ref, paragraph.getLastChild()); } + @Test + public void testRefWithEmphasisAround() { + // Emphasis around footnote reference, the * inside needs to be removed from emphasis processing + var doc = PARSER.parse("Test *abc [^foo*] def*\n\n[^foo*]: def\n"); + var ref = find(doc, FootnoteReference.class); + assertEquals("foo*", ref.getLabel()); + assertText("abc ", ref.getPrevious()); + assertText(" def", ref.getNext()); + var em = find(doc, Emphasis.class); + assertEquals(em, ref.getParent()); + } + @Test public void testRefAfterBang() { var doc = PARSER.parse("Test![^foo]\n\n[^foo]: def\n"); var ref = find(doc, FootnoteReference.class); assertEquals("foo", ref.getLabel()); var paragraph = doc.getFirstChild(); - var text = (Text) paragraph.getFirstChild(); - assertEquals("Test!", text.getLiteral()); + assertText("Test!", paragraph.getFirstChild()); } - // Interesting test cases: - - // Test [foo][^bar] - // - // [^bar]: footnote - // - // -> [^bar] is a footnote but [foo] is just text, because full reference links don't resolve as footnotes - - // Test [^bar][] - // - // [^bar]: footnote - // - // -> [^bar] is a footnote but [] is just text, because collapsed reference links don't resolve as footnotes + @Test + public void testRefAsLabelOnly() { + // [^bar] is a footnote but [foo] is just text, because full reference links (text `foo`, label `^bar`) don't + // resolve as footnotes. If `[foo][^bar]` fails to parse as a bracket, `[^bar]` by itself needs to be tried. + var doc = PARSER.parse("Test [foo][^bar]\n\n[^bar]: footnote\n"); + var ref = find(doc, FootnoteReference.class); + assertEquals("bar", ref.getLabel()); + var paragraph = doc.getFirstChild(); + assertText("Test [foo]", paragraph.getFirstChild()); + } - // Test [^f[oo] - // - // [^f[oo]: /url - // - // -> Not a footnote, [ needs to be escaped + @Test + public void testRefWithEmptyLabel() { + // [^bar] is a footnote but [] is just text, because collapsed reference links don't resolve as footnotes + var doc = PARSER.parse("Test [^bar][]\n\n[^bar]: footnote\n"); + var ref = find(doc, FootnoteReference.class); + assertEquals("bar", ref.getLabel()); + var paragraph = doc.getFirstChild(); + assertText("Test ", paragraph.getFirstChild()); + assertText("[]", paragraph.getLastChild()); + } - // Test [^*foo*] - // - // [^*foo*]: /url - // - // -> No emphasis inside footnote reference + @Test + public void testRefWithBracket() { + // Not a footnote, [ needs to be escaped + var doc = PARSER.parse("Test [^f[oo]\n\n[^f[oo]: /url\n"); + assertNull(tryFind(doc, FootnoteReference.class)); + } - // Test *abc [^foo*] def* - // - // [^foo*]: /url - // - // -> Emphasis around footnote reference + // Interesting test cases: // Test [^*foo*][foo] // @@ -187,12 +193,6 @@ public void testRefAfterBang() { // // [^*foo*]: /url - // Test ![^foo] - // - // [^foo]: note - // - // -> Not an image - private static T find(Node parent, Class nodeClass) { return Objects.requireNonNull(tryFind(parent, nodeClass), "Could not find a " + nodeClass.getSimpleName() + " node in " + parent); } @@ -212,4 +212,9 @@ private static List findAll(Node parent, Class nodeClass) { } return nodes; } + + private static void assertText(String expected, Node node) { + var text = (Text) node; + assertEquals(expected, text.getLiteral()); + } } From 947b1e549a17adac8f9485798396fb46e3f8a6ad Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 1 Jun 2024 17:02:16 +1000 Subject: [PATCH 116/345] Let full reference links override footnotes --- .../internal/FootnoteBracketProcessor.java | 7 +++++ .../ext/footnotes/FootnotesTest.java | 31 +++++++++++-------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java index d2fa434ac..8f64a750a 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java @@ -2,6 +2,7 @@ import org.commonmark.ext.footnotes.FootnoteDefinition; import org.commonmark.ext.footnotes.FootnoteReference; +import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.beta.BracketInfo; import org.commonmark.parser.beta.BracketProcessor; @@ -14,6 +15,12 @@ public BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlinePar // TODO: Does parsing need to be more strict here? var text = bracketInfo.text(); if (text.startsWith("^")) { + if (bracketInfo.label() != null && context.getDefinition(LinkReferenceDefinition.class, bracketInfo.label()) != null) { + // If there's a label after the text and the label has a definition -> it's a link, and it should + // take preference. + return BracketResult.none(); + } + var label = text.substring(1); // Check if we have a definition, otherwise ignore (same behavior as for link reference definitions) var def = context.getDefinition(FootnoteDefinition.class, label); diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index 570b39544..20ae6f5f6 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -179,19 +179,24 @@ public void testRefWithBracket() { assertNull(tryFind(doc, FootnoteReference.class)); } - // Interesting test cases: - - // Test [^*foo*][foo] - // - // [^*foo*]: /url - // - // [foo]: /url - // - // vs - // - // Test [^*foo*][foo] - // - // [^*foo*]: /url + @Test + public void testPreferReferenceLink() { + // This is tricky because `[^*foo*][foo]` is a valid link already. If `[foo]` was not defined, the first bracket + // would be a footnote. + var doc = PARSER.parse("Test [^*foo*][foo]\n\n[^*foo*]: /url\n\n[foo]: /url"); + assertNull(tryFind(doc, FootnoteReference.class)); + } + + @Test + public void testReferenceLinkWithoutDefinition() { + // Similar to previous test but there's no definition + var doc = PARSER.parse("Test [^*foo*][foo]\n\n[^*foo*]: def\n"); + var ref = find(doc, FootnoteReference.class); + assertEquals("*foo*", ref.getLabel()); + var paragraph = (Paragraph) doc.getFirstChild(); + assertText("Test ", paragraph.getFirstChild()); + assertText("[foo]", paragraph.getLastChild()); + } private static T find(Node parent, Class nodeClass) { return Objects.requireNonNull(tryFind(parent, nodeClass), "Could not find a " + nodeClass.getSimpleName() + " node in " + parent); From 05621db39e58d99887499fcdf4d08e85c429460a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 15 Jun 2024 00:41:41 +1000 Subject: [PATCH 117/345] Footnotes: HTML and Markdown rendering --- .../ext/footnotes/FootnotesExtension.java | 34 ++- .../internal/FootnoteHtmlNodeRenderer.java | 219 ++++++++++++++++++ .../FootnoteMarkdownNodeRenderer.java | 71 ++++++ .../footnotes/FootnoteHtmlRendererTest.java | 130 +++++++++++ .../FootnoteMarkdownRendererTest.java | 43 ++++ .../internal/renderer/NodeRendererMap.java | 20 +- .../org/commonmark/node/DefinitionMap.java | 7 +- .../org/commonmark/renderer/NodeRenderer.java | 6 + .../renderer/html/HtmlRenderer.java | 20 +- .../commonmark/renderer/html/HtmlWriter.java | 12 +- 10 files changed, 542 insertions(+), 20 deletions(-) create mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java create mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java create mode 100644 commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java create mode 100644 commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java index 60b72393a..1ec3b0f00 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java @@ -3,13 +3,23 @@ import org.commonmark.Extension; import org.commonmark.ext.footnotes.internal.FootnoteBlockParser; import org.commonmark.ext.footnotes.internal.FootnoteBracketProcessor; +import org.commonmark.ext.footnotes.internal.FootnoteHtmlNodeRenderer; +import org.commonmark.ext.footnotes.internal.FootnoteMarkdownNodeRenderer; import org.commonmark.parser.Parser; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; +import org.commonmark.renderer.markdown.MarkdownRenderer; + +import java.util.Set; /** * TODO */ -// TODO: HTML rendering and Markdown rendering -public class FootnotesExtension implements Parser.ParserExtension { +public class FootnotesExtension implements Parser.ParserExtension, + HtmlRenderer.HtmlRendererExtension, + MarkdownRenderer.MarkdownRendererExtension { private FootnotesExtension() { } @@ -24,4 +34,24 @@ public void extend(Parser.Builder parserBuilder) { .customBlockParserFactory(new FootnoteBlockParser.Factory()) .bracketProcessor(new FootnoteBracketProcessor()); } + + @Override + public void extend(HtmlRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(FootnoteHtmlNodeRenderer::new); + } + + @Override + public void extend(MarkdownRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new FootnoteMarkdownNodeRenderer(context); + } + + @Override + public Set getSpecialCharacters() { + return Set.of(); + } + }); + } } diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java new file mode 100644 index 000000000..4f36d0732 --- /dev/null +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java @@ -0,0 +1,219 @@ +package org.commonmark.ext.footnotes.internal; + +import org.commonmark.ext.footnotes.FootnoteDefinition; +import org.commonmark.ext.footnotes.FootnoteReference; +import org.commonmark.node.*; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlWriter; + +import java.util.*; + +/** + * HTML rendering for footnotes. + *

      + * Aims to match the rendering of cmark-gfm (which is slightly different from GitHub's when it comes to class + * attributes, not sure why). + *

      + * Some notes on how rendering works: + *

        + *
      • Footnotes are numbered according to the order of references, starting at 1
      • + *
      • Definitions are rendered at the end of the document, regardless of where the definition was in the source
      • + *
      • Definitions are ordered by number
      • + *
      • Definitions have links back to their references (one or more)
      • + *
      + */ +public class FootnoteHtmlNodeRenderer implements NodeRenderer { + + private final HtmlWriter html; + private final HtmlNodeRendererContext context; + + /** + * All definitions (even potentially unused ones), for looking up references + */ + private DefinitionMap definitions; + + // TODO: Might be nicer to have one map with a record instead. + /** + * Definitions that were referenced, and the reference IDs. In order in which they should be rendered. + */ + private final Map> references = new LinkedHashMap<>(); + /** + * The number of the definition (i.e. 1, 2). + */ + private final Map definitionNumbers = new HashMap<>(); + + public FootnoteHtmlNodeRenderer(HtmlNodeRendererContext context) { + this.html = context.getWriter(); + this.context = context; + } + + @Override + public Set> getNodeTypes() { + return Set.of(FootnoteReference.class, FootnoteDefinition.class); + } + + @Override + public void beforeRoot(Node node) { + // Collect definitions so we can look them up when encountering a reference later + var visitor = new FootnotesVisitor(); + node.accept(visitor); + definitions = visitor.definitions; + } + + @Override + public void render(Node node) { + if (node instanceof FootnoteReference) { + renderReference((FootnoteReference) node); + } + } + + private void renderReference(FootnoteReference ref) { + var def = definitions.get(ref.getLabel()); + if (def == null) { + // A reference without a corresponding definition is rendered as plain text + html.text("[^" + ref.getLabel() + "]"); + return; + } + + // The first referenced definition gets number 1, second one 2, etc. + var definitionNumber = definitionNumbers.computeIfAbsent(def, k -> definitionNumbers.size() + 1); + + var refs = references.computeIfAbsent(def, k -> new ArrayList<>()); + // The reference number for that particular definition. E.g. if there's two references for the same definition, + // the first one is 1, the second one 2, etc. This is needed to give each reference a unique ID so that each + // reference can get its own backlink from the definition. + var refNumber = refs.size() + 1; + var id = referenceId(def.getLabel(), refNumber); + refs.add(id); + + html.tag("sup", context.extendAttributes(ref, "sup", Map.of("class", "footnote-ref"))); + + var href = "#" + definitionId(def.getLabel()); + var attrs = new LinkedHashMap(); + attrs.put("href", href); + attrs.put("id", id); + attrs.put("data-footnote-ref", null); + html.tag("a", context.extendAttributes(ref, "a", attrs)); + html.raw(String.valueOf(definitionNumber)); + html.tag("/a"); + html.tag("/sup"); + } + + @Override + public void afterRoot(Node node) { + // Now render the referenced definitions if there are any + if (references.isEmpty()) { + return; + } + + var firstDef = references.keySet().iterator().next(); + var attrs = new LinkedHashMap(); + attrs.put("class", "footnotes"); + attrs.put("data-footnotes", null); + html.tag("section", context.extendAttributes(firstDef, "section", attrs)); + html.line(); + html.tag("ol"); + html.line(); + for (var entry : references.entrySet()) { + var def = entry.getKey(); + var refs = entry.getValue(); + int number = Objects.requireNonNull(definitionNumbers.get(def)); + renderDefinition(def, number, refs); + } + html.tag("/ol"); + html.line(); + html.tag("/section"); + html.line(); + } + + private void renderDefinition(FootnoteDefinition def, int defNumber, List references) { + //
        etc + var id = definitionId(def.getLabel()); + var attrs = new LinkedHashMap(); + attrs.put("id", id); + html.tag("li", context.extendAttributes(def, "li", attrs)); + html.line(); + + if (def.getLastChild() instanceof Paragraph) { + // Add backlinks into last paragraph before "p". This is what GFM does. + var lastParagraph = (Paragraph) def.getLastChild(); + renderChildren(def, lastParagraph); + + html.line(); + // This is a tiny bit strange, we're rendering the

        ourselves here instead of delegating the rendering. + // What if the rendering was overwritten to not use

        , or do something else entirely? + // TODO: I think it would be better if we rendered *all* paragraphs ourselves in this case, for consistency. + html.tag("p", context.extendAttributes(lastParagraph, "p", Map.of())); + renderChildren(lastParagraph, null); + html.raw(" "); + renderBackrefs(def, defNumber, references); + html.tag("/p"); + html.line(); + } else { + renderChildren(def, null); + html.line(); + renderBackrefs(def, defNumber, references); + } + + html.tag("/li"); + html.line(); + } + + private void renderBackrefs(FootnoteDefinition def, int defNumber, List refs) { + for (int i = 0; i < refs.size(); i++) { + var ref = refs.get(i); + var refNumber = i + 1; + var idx = defNumber + (refNumber > 1 ? ("-" + refNumber) : ""); + + var attrs = new LinkedHashMap(); + attrs.put("href", "#" + ref); + attrs.put("class", "footnote-backref"); + attrs.put("data-footnote-backref", null); + attrs.put("data-footnote-backref-idx", idx); + attrs.put("aria-label", "Back to reference " + idx); + html.tag("a", context.extendAttributes(def, "a", attrs)); + if (refNumber > 1) { + html.tag("sup", context.extendAttributes(def, "sup", Map.of("class", "footnote-ref"))); + html.raw(String.valueOf(refNumber)); + html.tag("/sup"); + } + // U+21A9 LEFTWARDS ARROW WITH HOOK + html.raw("↩"); + html.tag("/a"); + if (i + 1 < refs.size()) { + html.raw(" "); + } + } + } + + private String referenceId(String label, int number) { + return "fnref-" + label + (number == 1 ? "" : ("-" + number)); + } + + private String definitionId(String label) { + return "fn-" + label; + } + + private void renderChildren(Node parent, Node until) { + Node node = parent.getFirstChild(); + while (node != until) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } + + private static class FootnotesVisitor extends AbstractVisitor { + + private final DefinitionMap definitions = new DefinitionMap<>(FootnoteDefinition.class); + + @Override + public void visit(CustomBlock customBlock) { + if (customBlock instanceof FootnoteDefinition) { + var def = (FootnoteDefinition) customBlock; + definitions.putIfAbsent(def.getLabel(), def); + } + } + } +} diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java new file mode 100644 index 000000000..3d6515001 --- /dev/null +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java @@ -0,0 +1,71 @@ +package org.commonmark.ext.footnotes.internal; + +import org.commonmark.ext.footnotes.FootnoteDefinition; +import org.commonmark.ext.footnotes.FootnoteReference; +import org.commonmark.node.*; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlWriter; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownWriter; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Set; + +public class FootnoteMarkdownNodeRenderer implements NodeRenderer { + + private final MarkdownWriter writer; + private final MarkdownNodeRendererContext context; + + public FootnoteMarkdownNodeRenderer(MarkdownNodeRendererContext context) { + this.writer = context.getWriter(); + this.context = context; + } + + @Override + public Set> getNodeTypes() { + return Set.of(FootnoteReference.class, FootnoteDefinition.class); + } + + @Override + public void render(Node node) { + if (node instanceof FootnoteReference) { + renderReference((FootnoteReference) node); + } else if (node instanceof FootnoteDefinition) { + renderDefinition((FootnoteDefinition) node); + } + } + + private void renderReference(FootnoteReference ref) { + writer.raw("[^"); + // TODO: raw or text? Can the label contain characters that need to be escaped? + writer.raw(ref.getLabel()); + writer.raw("]"); + } + + private void renderDefinition(FootnoteDefinition def) { + writer.raw("[^"); + writer.raw(def.getLabel()); + writer.raw("]:"); + if (def.getFirstChild() instanceof Paragraph) { + writer.raw(" "); + } + + writer.pushPrefix(" "); + writer.pushTight(true); + renderChildren(def); + writer.popTight(); + writer.popPrefix(); + } + + private void renderChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } +} diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java new file mode 100644 index 000000000..b47e0ccba --- /dev/null +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java @@ -0,0 +1,130 @@ +package org.commonmark.ext.footnotes; + +import org.commonmark.Extension; +import org.commonmark.node.Document; +import org.commonmark.node.Paragraph; +import org.commonmark.node.Text; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.Asserts; +import org.commonmark.testutil.RenderingTestCase; +import org.junit.Test; + +import java.util.Set; + +public class FootnoteHtmlRendererTest extends RenderingTestCase { + private static final Set EXTENSIONS = Set.of(FootnotesExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void testOne() { + assertRendering("Test [^foo]\n\n[^foo]: note\n", + "

        Test 1

        \n" + + "
        \n" + + "
          \n" + + "
        1. \n" + + "

          note

          \n" + + "
        2. \n" + + "
        \n" + + "
        \n"); + } + + @Test + public void testLabelNormalization() { + // Labels match via their normalized form. For the href and IDs to match, rendering needs to use the + // label from the definition consistently. + assertRendering("Test [^bar]\n\n[^BAR]: note\n", + "

        Test 1

        \n" + + "
        \n" + + "
          \n" + + "
        1. \n" + + "

          note

          \n" + + "
        2. \n" + + "
        \n" + + "
        \n"); + } + + @Test + public void testMultipleReferences() { + // Tests a few things: + // - Numbering is based on the reference order, not the definition order + // - The same number is used when a definition is referenced multiple times + // - Multiple backrefs are rendered + assertRendering("First [^foo]\n\nThen [^bar]\n\nThen [^foo] again\n\n[^bar]: b\n[^foo]: f\n", + "

        First 1

        \n" + + "

        Then 2

        \n" + + "

        Then 1 again

        \n" + + "
        \n" + + "
          \n" + + "
        1. \n" + + "

          f 2

          \n" + + "
        2. \n" + + "
        3. \n" + + "

          b

          \n" + + "
        4. \n" + + "
        \n" + + "
        \n"); + } + + @Test + public void testDefinitionWithTwoParagraphs() { + // With two paragraphs, the backref should be added to the second one + assertRendering("Test [^foo]\n\n[^foo]: one\n \n two\n", + "

        Test 1

        \n" + + "
        \n" + + "
          \n" + + "
        1. \n" + + "

          one

          \n" + + "

          two

          \n" + + "
        2. \n" + + "
        \n" + + "
        \n"); + } + + @Test + public void testDefinitionWithList() { + assertRendering("Test [^foo]\n\n[^foo]:\n - one\n - two\n", + "

        Test 1

        \n" + + "
        \n" + + "
          \n" + + "
        1. \n" + + "
            \n" + + "
          • one
          • \n" + + "
          • two
          • \n" + + "
          \n" + + "
        2. \n" + + "
        \n" + + "
        \n"); + } + + @Test + public void testRenderNodesDirectly() { + // Everything should work as expected when rendering from nodes directly (no parsing step). + var doc = new Document(); + var p = new Paragraph(); + p.appendChild(new Text("Test ")); + p.appendChild(new FootnoteReference("foo")); + var def = new FootnoteDefinition("foo"); + var note = new Paragraph(); + note.appendChild(new Text("note!")); + def.appendChild(note); + doc.appendChild(p); + doc.appendChild(def); + + var expected = "

        Test 1

        \n" + + "
        \n" + + "
          \n" + + "
        1. \n" + + "

          note!

          \n" + + "
        2. \n" + + "
        \n" + + "
        \n"; + Asserts.assertRendering("", expected, RENDERER.render(doc)); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java new file mode 100644 index 000000000..f3a0efcb3 --- /dev/null +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java @@ -0,0 +1,43 @@ +package org.commonmark.ext.footnotes; + +import org.commonmark.Extension; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.markdown.MarkdownRenderer; +import org.junit.Test; + +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +public class FootnoteMarkdownRendererTest { + private static final Set EXTENSIONS = Set.of(FootnotesExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void testSimple() { + assertRoundTrip("Test [^foo]\n\n[^foo]: note\n"); + } + + @Test + public void testUnreferenced() { + // Whether a reference has a corresponding definition or vice versa shouldn't matter for Markdown rendering. + assertRoundTrip("Test [^foo]\n\n[^foo]: one\n[^bar]: two\n"); + } + + @Test + public void testFootnoteWithBlock() { + assertRoundTrip("Test [^foo]\n\n[^foo]: - foo\n - bar\n"); + } + + private void assertRoundTrip(String input) { + String rendered = parseAndRender(input); + assertEquals(input, rendered); + } + + private String parseAndRender(String source) { + Node parsed = PARSER.parse(source); + return RENDERER.render(parsed); + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java b/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java index e3adaa11f..95ac6ce46 100644 --- a/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java +++ b/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java @@ -3,24 +3,36 @@ import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class NodeRendererMap { + private final List nodeRenderers = new ArrayList<>(); private final Map, NodeRenderer> renderers = new HashMap<>(32); public void add(NodeRenderer nodeRenderer) { - for (Class nodeType : nodeRenderer.getNodeTypes()) { - // Overwrite existing renderer - renderers.put(nodeType, nodeRenderer); + nodeRenderers.add(nodeRenderer); + for (var nodeType : nodeRenderer.getNodeTypes()) { + // The first node renderer for a node type "wins". + renderers.putIfAbsent(nodeType, nodeRenderer); } } public void render(Node node) { - NodeRenderer nodeRenderer = renderers.get(node.getClass()); + var nodeRenderer = renderers.get(node.getClass()); if (nodeRenderer != null) { nodeRenderer.render(node); } } + + public void beforeRoot(Node node) { + nodeRenderers.forEach(r -> r.beforeRoot(node)); + } + + public void afterRoot(Node node) { + nodeRenderers.forEach(r -> r.afterRoot(node)); + } } diff --git a/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java b/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java index 82f553ff1..9cc94507f 100644 --- a/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java +++ b/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java @@ -33,13 +33,14 @@ public void addAll(DefinitionMap that) { } /** - * Store a new definition unless one is already in the map. + * Store a new definition unless one is already in the map. If there is no definition for that label, return null. + * Otherwise, return the existing definition. */ - public void putIfAbsent(String label, D definition) { + public D putIfAbsent(String label, D definition) { String normalizedLabel = Escaping.normalizeLabelContent(label); // spec: When there are multiple matching link reference definitions, the first is used - definitions.putIfAbsent(normalizedLabel, definition); + return definitions.putIfAbsent(normalizedLabel, definition); } /** diff --git a/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java index e2d5ebc96..193d5e267 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java @@ -20,4 +20,10 @@ public interface NodeRenderer { * @param node the node to render, will be an instance of one of {@link #getNodeTypes()} */ void render(Node node); + + default void beforeRoot(Node node) { + } + + default void afterRoot(Node node) { + } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java index 06ad01634..35db46a64 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java @@ -60,7 +60,9 @@ public static Builder builder() { public void render(Node node, Appendable output) { Objects.requireNonNull(node, "node must not be null"); RendererContext context = new RendererContext(new HtmlWriter(output)); + context.beforeRoot(node); context.render(node); + context.afterRoot(node); } @Override @@ -225,15 +227,13 @@ private RendererContext(HtmlWriter htmlWriter) { this.htmlWriter = htmlWriter; attributeProviders = new ArrayList<>(attributeProviderFactories.size()); - for (AttributeProviderFactory attributeProviderFactory : attributeProviderFactories) { + for (var attributeProviderFactory : attributeProviderFactories) { attributeProviders.add(attributeProviderFactory.create(this)); } - // The first node renderer for a node type "wins". - for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) { - HtmlNodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i); - NodeRenderer nodeRenderer = nodeRendererFactory.create(this); - nodeRendererMap.add(nodeRenderer); + for (var factory : nodeRendererFactories) { + var renderer = factory.create(this); + nodeRendererMap.add(renderer); } } @@ -283,6 +283,14 @@ public void render(Node node) { nodeRendererMap.render(node); } + public void beforeRoot(Node node) { + nodeRendererMap.beforeRoot(node); + } + + public void afterRoot(Node node) { + nodeRendererMap.afterRoot(node); + } + private void setCustomAttributes(Node node, String tagName, Map attrs) { for (AttributeProvider attributeProvider : attributeProviders) { attributeProvider.setAttributes(node, tagName, attrs); diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java index 7df185e48..a4ac05d45 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java @@ -38,12 +38,14 @@ public void tag(String name, Map attrs, boolean voidElement) { append("<"); append(name); if (attrs != null && !attrs.isEmpty()) { - for (Map.Entry attrib : attrs.entrySet()) { + for (var attr : attrs.entrySet()) { append(" "); - append(Escaping.escapeHtml(attrib.getKey())); - append("=\""); - append(Escaping.escapeHtml(attrib.getValue())); - append("\""); + append(Escaping.escapeHtml(attr.getKey())); + if (attr.getValue() != null) { + append("=\""); + append(Escaping.escapeHtml(attr.getValue())); + append("\""); + } } } if (voidElement) { From 4616d50689a2889753eb0719d5bdc2319b1aca37 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 15 Jun 2024 16:13:48 +1000 Subject: [PATCH 118/345] Use a single map --- .../internal/FootnoteHtmlNodeRenderer.java | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java index 4f36d0732..bc430e095 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java @@ -31,17 +31,12 @@ public class FootnoteHtmlNodeRenderer implements NodeRenderer { /** * All definitions (even potentially unused ones), for looking up references */ - private DefinitionMap definitions; + private DefinitionMap definitionMap; - // TODO: Might be nicer to have one map with a record instead. /** - * Definitions that were referenced, and the reference IDs. In order in which they should be rendered. + * Definitions that were referenced, in order in which they should be rendered. */ - private final Map> references = new LinkedHashMap<>(); - /** - * The number of the definition (i.e. 1, 2). - */ - private final Map definitionNumbers = new HashMap<>(); + private final Map referencedDefinitions = new LinkedHashMap<>(); public FootnoteHtmlNodeRenderer(HtmlNodeRendererContext context) { this.html = context.getWriter(); @@ -58,7 +53,7 @@ public void beforeRoot(Node node) { // Collect definitions so we can look them up when encountering a reference later var visitor = new FootnotesVisitor(); node.accept(visitor); - definitions = visitor.definitions; + definitionMap = visitor.definitions; } @Override @@ -69,7 +64,7 @@ public void render(Node node) { } private void renderReference(FootnoteReference ref) { - var def = definitions.get(ref.getLabel()); + var def = definitionMap.get(ref.getLabel()); if (def == null) { // A reference without a corresponding definition is rendered as plain text html.text("[^" + ref.getLabel() + "]"); @@ -77,15 +72,14 @@ private void renderReference(FootnoteReference ref) { } // The first referenced definition gets number 1, second one 2, etc. - var definitionNumber = definitionNumbers.computeIfAbsent(def, k -> definitionNumbers.size() + 1); - - var refs = references.computeIfAbsent(def, k -> new ArrayList<>()); + var referencedDef = referencedDefinitions.computeIfAbsent(def, k -> new ReferencedDefinition(referencedDefinitions.size() + 1)); + var definitionNumber = referencedDef.definitionNumber; // The reference number for that particular definition. E.g. if there's two references for the same definition, // the first one is 1, the second one 2, etc. This is needed to give each reference a unique ID so that each // reference can get its own backlink from the definition. - var refNumber = refs.size() + 1; + var refNumber = referencedDef.references.size() + 1; var id = referenceId(def.getLabel(), refNumber); - refs.add(id); + referencedDef.references.add(id); html.tag("sup", context.extendAttributes(ref, "sup", Map.of("class", "footnote-ref"))); @@ -103,11 +97,11 @@ private void renderReference(FootnoteReference ref) { @Override public void afterRoot(Node node) { // Now render the referenced definitions if there are any - if (references.isEmpty()) { + if (referencedDefinitions.isEmpty()) { return; } - var firstDef = references.keySet().iterator().next(); + var firstDef = referencedDefinitions.keySet().iterator().next(); var attrs = new LinkedHashMap(); attrs.put("class", "footnotes"); attrs.put("data-footnotes", null); @@ -115,11 +109,8 @@ public void afterRoot(Node node) { html.line(); html.tag("ol"); html.line(); - for (var entry : references.entrySet()) { - var def = entry.getKey(); - var refs = entry.getValue(); - int number = Objects.requireNonNull(definitionNumbers.get(def)); - renderDefinition(def, number, refs); + for (var entry : referencedDefinitions.entrySet()) { + renderDefinition(entry.getKey(), entry.getValue()); } html.tag("/ol"); html.line(); @@ -127,7 +118,7 @@ public void afterRoot(Node node) { html.line(); } - private void renderDefinition(FootnoteDefinition def, int defNumber, List references) { + private void renderDefinition(FootnoteDefinition def, ReferencedDefinition referencedDefinition) { //
          etc var id = definitionId(def.getLabel()); var attrs = new LinkedHashMap(); @@ -147,24 +138,25 @@ private void renderDefinition(FootnoteDefinition def, int defNumber, List refs) { + private void renderBackrefs(FootnoteDefinition def, ReferencedDefinition referencedDefinition) { + var refs = referencedDefinition.references; for (int i = 0; i < refs.size(); i++) { var ref = refs.get(i); var refNumber = i + 1; - var idx = defNumber + (refNumber > 1 ? ("-" + refNumber) : ""); + var idx = referencedDefinition.definitionNumber + (refNumber > 1 ? ("-" + refNumber) : ""); var attrs = new LinkedHashMap(); attrs.put("href", "#" + ref); @@ -216,4 +208,19 @@ public void visit(CustomBlock customBlock) { } } } + + private static class ReferencedDefinition { + /** + * The definition number, starting from 1, and in order in which they're referenced. + */ + final int definitionNumber; + /** + * The IDs of references for this definition, for backrefs. + */ + final List references = new ArrayList<>(); + + ReferencedDefinition(int definitionNumber) { + this.definitionNumber = definitionNumber; + } + } } From b10bd57bbdaf363ec19cab8f76d4a60bdb6bc847 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 15 Jun 2024 16:21:03 +1000 Subject: [PATCH 119/345] Address TODO for paragraph rendering --- .../internal/FootnoteHtmlNodeRenderer.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java index bc430e095..17a46af0a 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java @@ -129,20 +129,29 @@ private void renderDefinition(FootnoteDefinition def, ReferencedDefinition refer if (def.getLastChild() instanceof Paragraph) { // Add backlinks into last paragraph before "p". This is what GFM does. var lastParagraph = (Paragraph) def.getLastChild(); - renderChildren(def, lastParagraph); + var node = def.getFirstChild(); + while (node != lastParagraph) { + if (node instanceof Paragraph) { + // Because we're manually rendering the

          for the last paragraph, do the same for all other + // paragraphs for consistency (Paragraph rendering might be overwritten by a custom renderer). + html.tag("p", context.extendAttributes(node, "p", Map.of())); + renderChildren(node); + html.tag("/p"); + html.line(); + } else { + context.render(node); + } + node = node.getNext(); + } - html.line(); - // This is a tiny bit strange, we're rendering the

          ourselves here instead of delegating the rendering. - // What if the rendering was overwritten to not use

          , or do something else entirely? - // TODO: I think it would be better if we rendered *all* paragraphs ourselves in this case, for consistency. html.tag("p", context.extendAttributes(lastParagraph, "p", Map.of())); - renderChildren(lastParagraph, null); + renderChildren(lastParagraph); html.raw(" "); renderBackrefs(def, referencedDefinition); html.tag("/p"); html.line(); } else { - renderChildren(def, null); + renderChildren(def); html.line(); renderBackrefs(def, referencedDefinition); } @@ -187,9 +196,9 @@ private String definitionId(String label) { return "fn-" + label; } - private void renderChildren(Node parent, Node until) { + private void renderChildren(Node parent) { Node node = parent.getFirstChild(); - while (node != until) { + while (node != null) { Node next = node.getNext(); context.render(node); node = next; From 7fe1a9ef3d01bc431209de289ec8a62808cee885 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 20 Jun 2024 22:07:45 +1000 Subject: [PATCH 120/345] Add exclude list for label characters See `_scan_footnote_definition` in cmark-gfm. --- .../internal/FootnoteBlockParser.java | 23 +++++++++++-------- .../ext/footnotes/FootnotesTest.java | 13 ++++------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java index 55ca86993..be85c5ef4 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java @@ -71,17 +71,20 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar for (index = labelStart; index < content.length(); index++) { var c = content.charAt(index); - if (c == ']' && index + 1 < content.length() && content.charAt(index + 1) == ':') { - if (index > labelStart) { - var label = content.subSequence(labelStart, index).toString(); - return BlockStart.of(new FootnoteBlockParser(label)).atIndex(index + 2); - } else { + switch (c) { + case ']': + if (index > labelStart && index + 1 < content.length() && content.charAt(index + 1) == ':') { + var label = content.subSequence(labelStart, index).toString(); + return BlockStart.of(new FootnoteBlockParser(label)).atIndex(index + 2); + } else { + return BlockStart.none(); + } + case ' ': + case '\r': + case '\n': + case '\0': + case '\t': return BlockStart.none(); - } - } - // TODO: Check what GitHub actually does here, e.g. tabs, control characters, other Unicode whitespace - if (Character.isWhitespace(c)) { - return BlockStart.none(); } } diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index 20ae6f5f6..4bacccd9b 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -20,20 +20,17 @@ public class FootnotesTest { @Test public void testDefBlockStart() { - for (var s : List.of("1", "a")) { + for (var s : List.of("1", "a", "^", "*", "\\a", "\uD83D\uDE42", "&")) { var doc = PARSER.parse("[^" + s + "]: footnote\n"); var def = find(doc, FootnoteDefinition.class); - // TODO: Should label be "^1" instead? assertEquals(s, def.getLabel()); } - for (var s : List.of("", " ", "a b")) { - var doc = PARSER.parse("[^" + s + "]: footnote\n"); - assertNull(tryFind(doc, FootnoteDefinition.class)); + for (var s : List.of("", " ", "a b", "]", "\r", "\n", "\t")) { + var input = "[^" + s + "]: footnote\n"; + var doc = PARSER.parse(input); + assertNull("input: " + input, tryFind(doc, FootnoteDefinition.class)); } - - // TODO: Test what characters are allowed for the label, e.g. - // [^], [^ ], [^^], [^[], [^*], [^\], [^\a], [^🙂], tab?, [^&], [^&] } @Test From 47e622e5434e0f231e8658e231456d506f171605 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 20 Jun 2024 22:13:37 +1000 Subject: [PATCH 121/345] Check for indentation --- .../ext/footnotes/internal/FootnoteBlockParser.java | 7 ++++--- .../java/org/commonmark/ext/footnotes/FootnotesTest.java | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java index be85c5ef4..dabaf599d 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java @@ -54,9 +54,11 @@ public static class Factory implements BlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { - var content = state.getLine().getContent(); - // TODO: Can it be indented? Maybe less than code block indent. + if (state.getIndent() >= 4) { + return BlockStart.none(); + } var index = state.getNextNonSpaceIndex(); + var content = state.getLine().getContent(); if (content.charAt(index) != '[' || index + 1 >= content.length()) { return BlockStart.none(); } @@ -66,7 +68,6 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } // Now at first label character (if any) index++; - var labelStart = index; for (index = labelStart; index < content.length(); index++) { diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index 4bacccd9b..10ecb184e 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -43,6 +43,14 @@ public void testDefBlockStartInterrupts() { assertEquals("1", def.getLabel()); } + @Test + public void testDefBlockStartIndented() { + var doc1 = PARSER.parse(" [^1]: footnote\n"); + assertEquals("1", find(doc1, FootnoteDefinition.class).getLabel()); + var doc2 = PARSER.parse(" [^1]: footnote\n"); + assertNull(tryFind(doc2, FootnoteDefinition.class)); + } + @Test public void testDefMultiple() { var doc = PARSER.parse("[^1]: foo\n[^2]: bar\n"); From 194067b26a54788b5fa68b92cff1490bf03fa461 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 20 Jun 2024 22:22:50 +1000 Subject: [PATCH 122/345] Address TODO in bracket processor --- .../internal/FootnoteBracketProcessor.java | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java index 8f64a750a..866980587 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java @@ -12,25 +12,29 @@ public class FootnoteBracketProcessor implements BracketProcessor { @Override public BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlineParserContext context) { - // TODO: Does parsing need to be more strict here? var text = bracketInfo.text(); - if (text.startsWith("^")) { - if (bracketInfo.label() != null && context.getDefinition(LinkReferenceDefinition.class, bracketInfo.label()) != null) { - // If there's a label after the text and the label has a definition -> it's a link, and it should - // take preference. - return BracketResult.none(); - } + if (!text.startsWith("^")) { + // Footnote reference needs to start with [^ + return BracketResult.none(); + } + + if (bracketInfo.label() != null && context.getDefinition(LinkReferenceDefinition.class, bracketInfo.label()) != null) { + // If there's a label after the text and the label has a definition -> it's a link, and it should take + // preference, e.g. in `[^foo][bar]` if `[bar]` has a definition, `[^foo]` won't be a footnote reference. + return BracketResult.none(); + } - var label = text.substring(1); - // Check if we have a definition, otherwise ignore (same behavior as for link reference definitions) - var def = context.getDefinition(FootnoteDefinition.class, label); - if (def != null) { - // For footnotes, we only ever consume the text part of the link, not the label part (if any). - var position = bracketInfo.afterTextBracket(); - // If the marker is `![`, we don't want to include the `!`, so start from bracket - return BracketResult.replaceWith(new FootnoteReference(label), position).startFromBracket(); - } + var label = text.substring(1); + // Check if we have a definition, otherwise ignore (same behavior as for link reference definitions). + // Note that the definition parser already checked the syntax of the label, we don't need to check again. + var def = context.getDefinition(FootnoteDefinition.class, label); + if (def == null) { + return BracketResult.none(); } - return BracketResult.none(); + + // For footnotes, we only ever consume the text part of the link, not the label part (if any) + var position = bracketInfo.afterTextBracket(); + // If the marker is `![`, we don't want to include the `!`, so start from bracket + return BracketResult.replaceWith(new FootnoteReference(label), position).startFromBracket(); } } From cbe592560a38e02fc49c966fb94d3df3012b77d8 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 22 Jun 2024 21:46:46 +1000 Subject: [PATCH 123/345] Also use processor for inline links --- .../internal/FootnoteBracketProcessor.java | 5 + .../ext/footnotes/FootnotesTest.java | 6 + .../commonmark/internal/InlineParserImpl.java | 176 +++++++++++------- .../internal/inline/CoreBracketProcessor.java | 21 ++- .../main/java/org/commonmark/node/Link.java | 3 + .../commonmark/parser/beta/BracketInfo.java | 10 + 6 files changed, 145 insertions(+), 76 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java index 866980587..c05800b29 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java @@ -12,6 +12,11 @@ public class FootnoteBracketProcessor implements BracketProcessor { @Override public BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlineParserContext context) { + if (bracketInfo.destination() != null) { + // If it's an inline link, it can't be a footnote reference + return BracketResult.none(); + } + var text = bracketInfo.text(); if (!text.startsWith("^")) { // Footnote reference needs to start with [^ diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index 10ecb184e..a44b6dbaa 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -177,6 +177,12 @@ public void testRefWithEmptyLabel() { assertText("[]", paragraph.getLastChild()); } + @Test + public void testInlineLinkTakesPrecedence() { + var doc = PARSER.parse("Test [^bar](/url)\n\n[^bar]: footnote\n"); + assertNull(tryFind(doc, FootnoteReference.class)); + } + @Test public void testRefWithBracket() { // Not a footnote, [ needs to be escaped diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index cf77f94aa..e1c021f76 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -323,82 +323,13 @@ private Node parseCloseBracket() { } private Node parseLinkOrImage(Bracket opener, Position beforeClose) { - // Check to see if we have a link (or image, with a ! in front). The different types: - // - Inline: `[foo](/uri)` or with optional title `[foo](/uri "title")` - // - Reference links - // - Full: `[foo][bar]` (foo is the text and bar is the label that needs to match a reference) - // - Collapsed: `[foo][]` (foo is both the text and label) - // - Shortcut: `[foo]` (foo is both the text and label) - - // Starting position is after the closing `]` - Position afterClose = scanner.position(); - - // Maybe an inline link/image - var destinationTitle = parseInlineDestinationTitle(scanner); - if (destinationTitle != null) { - var linkOrImage = opener.image - ? new Image(destinationTitle.destination, destinationTitle.title) - : new Link(destinationTitle.destination, destinationTitle.title); - return processLinkOrImage(opener, linkOrImage, false); - } - // Not an inline link/image, rewind back to after `]`. - scanner.setPosition(afterClose); - - // Maybe a reference link/image like `[foo][bar]`, `[foo][]` or `[foo]`. - // Note that even `[foo](` could be a valid link if foo is a reference, which is why we try this even if the `(` - // failed to be parsed as an inline link/image before. - - String text = scanner.getSource(opener.contentPosition, beforeClose).getContent(); - - // See if there's a link label like `[bar]` or `[]` - String label = parseLinkLabel(scanner); - if (label == null) { - // No label, rewind back - scanner.setPosition(afterClose); - } - var referenceType = label == null ? - BracketInfo.ReferenceType.SHORTCUT : label.isEmpty() ? - BracketInfo.ReferenceType.COLLAPSED : - BracketInfo.ReferenceType.FULL; - if ((referenceType == BracketInfo.ReferenceType.SHORTCUT || referenceType == BracketInfo.ReferenceType.COLLAPSED) - && opener.bracketAfter) { - // In case of SHORTCUT or COLLAPSED, the text is used as the reference. But the reference is not allowed to - // contain an unescaped bracket, so if that's the case we don't need to continue. This is an optimization. + var bracketInfo = parseBracketInfo(opener, beforeClose); + if (bracketInfo == null) { return null; } - - var bracketInfo = new BracketInfo() { - @Override - public OpenerType openerType() { - return opener.image ? OpenerType.IMAGE : OpenerType.LINK; - } - - @Override - public ReferenceType referenceType() { - return referenceType; - } - - @Override - public String text() { - return text; - } - - @Override - public String label() { - return label; - } - - @Override - public Position afterTextBracket() { - return afterClose; - } - }; - var processorStartPosition = scanner.position(); for (var bracketProcessor : bracketProcessors) { - // TODO: Should inline links also go through this, maybe? That would allow e.g. the image attributes extension - // to use it to parse the attributes, I think. It would also be clearer: Every type of link goes through this. var bracketResult = bracketProcessor.process(bracketInfo, scanner, context); if (!(bracketResult instanceof BracketResultImpl)) { // Reset position in case the processor used the scanner, and it didn't work out. @@ -438,6 +369,52 @@ public Position afterTextBracket() { return null; } + private BracketInfo parseBracketInfo(Bracket opener, Position beforeClose) { + // Check to see if we have a link (or image, with a ! in front). The different types: + // - Inline: `[foo](/uri)` or with optional title `[foo](/uri "title")` + // - Reference links + // - Full: `[foo][bar]` (foo is the text and bar is the label that needs to match a reference) + // - Collapsed: `[foo][]` (foo is both the text and label) + // - Shortcut: `[foo]` (foo is both the text and label) + + var openerType = opener.image ? BracketInfo.OpenerType.IMAGE : BracketInfo.OpenerType.LINK; + String text = scanner.getSource(opener.contentPosition, beforeClose).getContent(); + + // Starting position is after the closing `]` + Position afterClose = scanner.position(); + + // Maybe an inline link/image + var destinationTitle = parseInlineDestinationTitle(scanner); + if (destinationTitle != null) { + return new BracketInfoImpl(openerType, null, text, null, destinationTitle.destination, destinationTitle.title, afterClose); + } + // Not an inline link/image, rewind back to after `]`. + scanner.setPosition(afterClose); + + // Maybe a reference link/image like `[foo][bar]`, `[foo][]` or `[foo]`. + // Note that even `[foo](` could be a valid link if foo is a reference, which is why we try this even if the `(` + // failed to be parsed as an inline link/image before. + + // See if there's a link label like `[bar]` or `[]` + String label = parseLinkLabel(scanner); + if (label == null) { + // No label, rewind back + scanner.setPosition(afterClose); + } + var referenceType = label == null ? + BracketInfo.ReferenceType.SHORTCUT : label.isEmpty() ? + BracketInfo.ReferenceType.COLLAPSED : + BracketInfo.ReferenceType.FULL; + if ((referenceType == BracketInfo.ReferenceType.SHORTCUT || referenceType == BracketInfo.ReferenceType.COLLAPSED) + && opener.bracketAfter) { + // In case of SHORTCUT or COLLAPSED, the text is used as the reference. But the reference is not allowed to + // contain an unescaped bracket, so if that's the case we don't need to continue. This is an optimization. + return null; + } + + return new BracketInfoImpl(openerType, referenceType, text, label, null, null, afterClose); + } + private Node processLinkOrImage(Bracket opener, Node linkOrImage, boolean startFromBracket) { // Add all nodes between the opening bracket and now (closing bracket) as child nodes of the link Node node = opener.bracketNode.getNext(); @@ -893,4 +870,61 @@ public DestinationTitle(String destination, String title) { this.title = title; } } + + private static class BracketInfoImpl implements BracketInfo { + + private final OpenerType openerType; + private final ReferenceType referenceType; + private final String text; + private final String label; + private final String destination; + private final String title; + private final Position afterTextBracket; + + private BracketInfoImpl(OpenerType openerType, ReferenceType referenceType, String text, String label, + String destination, String title, Position afterTextBracket) { + this.openerType = openerType; + this.referenceType = referenceType; + this.text = text; + this.label = label; + this.destination = destination; + this.title = title; + this.afterTextBracket = afterTextBracket; + } + + @Override + public OpenerType openerType() { + return openerType; + } + + @Override + public ReferenceType referenceType() { + return referenceType; + } + + @Override + public String text() { + return text; + } + + @Override + public String label() { + return label; + } + + @Override + public String destination() { + return destination; + } + + @Override + public String title() { + return title; + } + + @Override + public Position afterTextBracket() { + return afterTextBracket; + } + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java index ce5fc27af..83040302b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java @@ -3,6 +3,7 @@ import org.commonmark.node.Image; import org.commonmark.node.Link; import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.node.Node; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.beta.BracketInfo; import org.commonmark.parser.beta.BracketProcessor; @@ -13,16 +14,26 @@ public class CoreBracketProcessor implements BracketProcessor { @Override public BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlineParserContext context) { + if (bracketInfo.destination() != null) { + // Inline link + var node = createNode(bracketInfo, bracketInfo.destination(), bracketInfo.title()); + return BracketResult.wrapTextIn(node, scanner.position()); + } + var label = bracketInfo.label(); var ref = label != null && !label.isEmpty() ? label : bracketInfo.text(); var def = context.getDefinition(LinkReferenceDefinition.class, ref); if (def != null) { - if (bracketInfo.openerType() == BracketInfo.OpenerType.IMAGE) { - return BracketResult.wrapTextIn(new Image(def.getDestination(), def.getTitle()), scanner.position()); - } else if (bracketInfo.openerType() == BracketInfo.OpenerType.LINK) { - return BracketResult.wrapTextIn(new Link(def.getDestination(), def.getTitle()), scanner.position()); - } + // Reference link + var node = createNode(bracketInfo, def.getDestination(), def.getTitle()); + return BracketResult.wrapTextIn(node, scanner.position()); } return BracketResult.none(); } + + private static Node createNode(BracketInfo bracketInfo, String destination, String title) { + return bracketInfo.openerType() == BracketInfo.OpenerType.IMAGE ? + new Image(destination, title) : + new Link(destination, title); + } } diff --git a/commonmark/src/main/java/org/commonmark/node/Link.java b/commonmark/src/main/java/org/commonmark/node/Link.java index b2ed8c2a1..0d1447b26 100644 --- a/commonmark/src/main/java/org/commonmark/node/Link.java +++ b/commonmark/src/main/java/org/commonmark/node/Link.java @@ -46,6 +46,9 @@ public void setDestination(String destination) { this.destination = destination; } + /** + * @return the title or null + */ public String getTitle() { return title; } diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java b/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java index 10002c91b..cdeea92cc 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java @@ -29,5 +29,15 @@ enum ReferenceType { */ String label(); + /** + * The destination if available, e.g. in `[foo](destination)`, or null + */ + String destination(); + + /** + * The title if available, e.g. in `[foo](destination "title")`, or null + */ + String title(); + Position afterTextBracket(); } From 982e6a59e2ee564b72819f3ee4357daae0801adf Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 22 Jun 2024 22:15:20 +1000 Subject: [PATCH 124/345] Set source span, rename methods --- .../ext/footnotes/FootnotesTest.java | 23 ++++++-- .../commonmark/internal/InlineParserImpl.java | 59 +++++++++++-------- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index a44b6dbaa..11b0d3bbe 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -2,6 +2,7 @@ import org.commonmark.Extension; import org.commonmark.node.*; +import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; import org.junit.Test; @@ -178,15 +179,15 @@ public void testRefWithEmptyLabel() { } @Test - public void testInlineLinkTakesPrecedence() { - var doc = PARSER.parse("Test [^bar](/url)\n\n[^bar]: footnote\n"); + public void testRefWithBracket() { + // Not a footnote, [ needs to be escaped + var doc = PARSER.parse("Test [^f[oo]\n\n[^f[oo]: /url\n"); assertNull(tryFind(doc, FootnoteReference.class)); } @Test - public void testRefWithBracket() { - // Not a footnote, [ needs to be escaped - var doc = PARSER.parse("Test [^f[oo]\n\n[^f[oo]: /url\n"); + public void testPreferInlineLink() { + var doc = PARSER.parse("Test [^bar](/url)\n\n[^bar]: footnote\n"); assertNull(tryFind(doc, FootnoteReference.class)); } @@ -209,6 +210,18 @@ public void testReferenceLinkWithoutDefinition() { assertText("[foo]", paragraph.getLastChild()); } + @Test + public void testSourcePositions() { + var parser = Parser.builder().extensions(EXTENSIONS).includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES).build(); + + var doc = parser.parse("Test [^foo]\n\n[^foo]: /url\n"); + var ref = find(doc, FootnoteReference.class); + assertEquals(ref.getSourceSpans(), List.of(SourceSpan.of(0, 5, 6))); + + var def = find(doc, FootnoteDefinition.class); + assertEquals(def.getSourceSpans(), List.of(SourceSpan.of(2, 0, 12))); + } + private static T find(Node parent, Class nodeClass) { return Objects.requireNonNull(tryFind(parent, nodeClass), "Could not find a " + nodeClass.getSimpleName() + " node in " + parent); } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index e1c021f76..22c8181b3 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -345,24 +345,10 @@ private Node parseLinkOrImage(Bracket opener, Position beforeClose) { switch (result.getType()) { case WRAP: scanner.setPosition(position); - return processLinkOrImage(opener, node, startFromBracket); + return wrapBracket(opener, node, startFromBracket); case REPLACE: scanner.setPosition(position); - - // Remove delimiters (but keep text nodes) - while (lastDelimiter != null && lastDelimiter != opener.previousDelimiter) { - removeDelimiterKeepNode(lastDelimiter); - } - - removeLastBracket(); - - Node n = opener.bangNode == null || startFromBracket ? opener.bracketNode : opener.bangNode; - while (n != null) { - var next = n.getNext(); - n.unlink(); - n = next; - } - return node; + return replaceBracket(opener, node, startFromBracket); } } @@ -415,23 +401,23 @@ private BracketInfo parseBracketInfo(Bracket opener, Position beforeClose) { return new BracketInfoImpl(openerType, referenceType, text, label, null, null, afterClose); } - private Node processLinkOrImage(Bracket opener, Node linkOrImage, boolean startFromBracket) { + private Node wrapBracket(Bracket opener, Node wrapperNode, boolean startFromBracket) { // Add all nodes between the opening bracket and now (closing bracket) as child nodes of the link - Node node = opener.bracketNode.getNext(); - while (node != null) { - Node next = node.getNext(); - linkOrImage.appendChild(node); - node = next; + Node n = opener.bracketNode.getNext(); + while (n != null) { + Node next = n.getNext(); + wrapperNode.appendChild(n); + n = next; } if (includeSourceSpans) { var startPosition = opener.bangPosition == null || startFromBracket ? opener.bracketPosition : opener.bangPosition; - linkOrImage.setSourceSpans(scanner.getSource(startPosition, scanner.position()).getSourceSpans()); + wrapperNode.setSourceSpans(scanner.getSource(startPosition, scanner.position()).getSourceSpans()); } // Process delimiters such as emphasis inside link/image processDelimiters(opener.previousDelimiter); - mergeChildTextNodes(linkOrImage); + mergeChildTextNodes(wrapperNode); // We don't need the corresponding text node anymore, we turned it into a link/image node if (opener.bangNode != null && !startFromBracket) { opener.bangNode.unlink(); @@ -451,7 +437,30 @@ private Node processLinkOrImage(Bracket opener, Node linkOrImage, boolean startF } } - return linkOrImage; + return wrapperNode; + } + + private Node replaceBracket(Bracket opener, Node node, boolean startFromBracket) { + // Remove delimiters (but keep text nodes) + while (lastDelimiter != null && lastDelimiter != opener.previousDelimiter) { + removeDelimiterKeepNode(lastDelimiter); + } + + if (includeSourceSpans) { + var startPosition = opener.bangPosition == null || startFromBracket ? opener.bracketPosition : opener.bangPosition; + node.setSourceSpans(scanner.getSource(startPosition, scanner.position()).getSourceSpans()); + } + + removeLastBracket(); + + // Remove nodes that we added since the opener, because we're replacing them + Node n = opener.bangNode == null || startFromBracket ? opener.bracketNode : opener.bangNode; + while (n != null) { + var next = n.getNext(); + n.unlink(); + n = next; + } + return node; } private void addBracket(Bracket bracket) { From 407d0e09cdf850bfc39101ebde8c47c1e76abc6a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 29 Jun 2024 11:31:43 +1000 Subject: [PATCH 125/345] Address TODO in markdown renderer --- .../internal/FootnoteMarkdownNodeRenderer.java | 2 +- .../ext/footnotes/FootnoteMarkdownRendererTest.java | 5 +++++ .../org/commonmark/ext/footnotes/FootnotesTest.java | 11 ++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java index 3d6515001..956cd1de1 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java @@ -40,7 +40,7 @@ public void render(Node node) { private void renderReference(FootnoteReference ref) { writer.raw("[^"); - // TODO: raw or text? Can the label contain characters that need to be escaped? + // The label is parsed as-is without escaping, so we can render it back as-is writer.raw(ref.getLabel()); writer.raw("]"); } diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java index f3a0efcb3..2ab2ba0eb 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java @@ -31,6 +31,11 @@ public void testFootnoteWithBlock() { assertRoundTrip("Test [^foo]\n\n[^foo]: - foo\n - bar\n"); } + @Test + public void testBackslashInLabel() { + assertRoundTrip("[^\\foo]\n\n[^\\foo]: note\n"); + } + private void assertRoundTrip(String input) { String rendered = parseAndRender(input); assertEquals(input, rendered); diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index 11b0d3bbe..73e659aa7 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -21,7 +21,7 @@ public class FootnotesTest { @Test public void testDefBlockStart() { - for (var s : List.of("1", "a", "^", "*", "\\a", "\uD83D\uDE42", "&")) { + for (var s : List.of("1", "a", "^", "*", "\\a", "\uD83D\uDE42", "&0")) { var doc = PARSER.parse("[^" + s + "]: footnote\n"); var def = find(doc, FootnoteDefinition.class); assertEquals(s, def.getLabel()); @@ -185,6 +185,15 @@ public void testRefWithBracket() { assertNull(tryFind(doc, FootnoteReference.class)); } + @Test + public void testRefWithBackslash() { + var doc = PARSER.parse("[^\\foo]\n\n[^\\foo]: note\n"); + var ref = find(doc, FootnoteReference.class); + assertEquals("\\foo", ref.getLabel()); + var def = find(doc, FootnoteDefinition.class); + assertEquals("\\foo", def.getLabel()); + } + @Test public void testPreferInlineLink() { var doc = PARSER.parse("Test [^bar](/url)\n\n[^bar]: footnote\n"); From e619eacb4dcfcb5346d3fd3726e020f9907020b1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 29 Jun 2024 11:45:47 +1000 Subject: [PATCH 126/345] Rename BracketProcessor to LinkProcessor It started out limited but now it covers all types of links/images, knows about destination and title, etc. --- .../ext/footnotes/FootnotesExtension.java | 4 +- ...cessor.java => FootnoteLinkProcessor.java} | 28 ++++++------- .../commonmark/internal/DocumentParser.java | 10 ++--- .../internal/InlineParserContextImpl.java | 12 +++--- .../commonmark/internal/InlineParserImpl.java | 42 +++++++++---------- .../internal/inline/CoreBracketProcessor.java | 39 ----------------- .../internal/inline/CoreLinkProcessor.java | 39 +++++++++++++++++ ...ketResultImpl.java => LinkResultImpl.java} | 8 ++-- .../parser/InlineParserContext.java | 4 +- .../java/org/commonmark/parser/Parser.java | 18 ++++---- .../parser/beta/BracketProcessor.java | 8 ---- .../commonmark/parser/beta/BracketResult.java | 20 --------- .../beta/{BracketInfo.java => LinkInfo.java} | 2 +- .../commonmark/parser/beta/LinkProcessor.java | 8 ++++ .../commonmark/parser/beta/LinkResult.java | 20 +++++++++ .../test/InlineParserContextTest.java | 6 +-- 16 files changed, 134 insertions(+), 134 deletions(-) rename commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/{FootnoteBracketProcessor.java => FootnoteLinkProcessor.java} (60%) delete mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/CoreLinkProcessor.java rename commonmark/src/main/java/org/commonmark/internal/inline/{BracketResultImpl.java => LinkResultImpl.java} (76%) delete mode 100644 commonmark/src/main/java/org/commonmark/parser/beta/BracketProcessor.java delete mode 100644 commonmark/src/main/java/org/commonmark/parser/beta/BracketResult.java rename commonmark/src/main/java/org/commonmark/parser/beta/{BracketInfo.java => LinkInfo.java} (96%) create mode 100644 commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java create mode 100644 commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java index 1ec3b0f00..263b52b9c 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java @@ -2,7 +2,7 @@ import org.commonmark.Extension; import org.commonmark.ext.footnotes.internal.FootnoteBlockParser; -import org.commonmark.ext.footnotes.internal.FootnoteBracketProcessor; +import org.commonmark.ext.footnotes.internal.FootnoteLinkProcessor; import org.commonmark.ext.footnotes.internal.FootnoteHtmlNodeRenderer; import org.commonmark.ext.footnotes.internal.FootnoteMarkdownNodeRenderer; import org.commonmark.parser.Parser; @@ -32,7 +32,7 @@ public static Extension create() { public void extend(Parser.Builder parserBuilder) { parserBuilder .customBlockParserFactory(new FootnoteBlockParser.Factory()) - .bracketProcessor(new FootnoteBracketProcessor()); + .linkProcessor(new FootnoteLinkProcessor()); } @Override diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java similarity index 60% rename from commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java rename to commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java index c05800b29..1af57893f 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBracketProcessor.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java @@ -4,29 +4,29 @@ import org.commonmark.ext.footnotes.FootnoteReference; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParserContext; -import org.commonmark.parser.beta.BracketInfo; -import org.commonmark.parser.beta.BracketProcessor; -import org.commonmark.parser.beta.BracketResult; +import org.commonmark.parser.beta.LinkInfo; +import org.commonmark.parser.beta.LinkProcessor; +import org.commonmark.parser.beta.LinkResult; import org.commonmark.parser.beta.Scanner; -public class FootnoteBracketProcessor implements BracketProcessor { +public class FootnoteLinkProcessor implements LinkProcessor { @Override - public BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlineParserContext context) { - if (bracketInfo.destination() != null) { + public LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContext context) { + if (linkInfo.destination() != null) { // If it's an inline link, it can't be a footnote reference - return BracketResult.none(); + return LinkResult.none(); } - var text = bracketInfo.text(); + var text = linkInfo.text(); if (!text.startsWith("^")) { // Footnote reference needs to start with [^ - return BracketResult.none(); + return LinkResult.none(); } - if (bracketInfo.label() != null && context.getDefinition(LinkReferenceDefinition.class, bracketInfo.label()) != null) { + if (linkInfo.label() != null && context.getDefinition(LinkReferenceDefinition.class, linkInfo.label()) != null) { // If there's a label after the text and the label has a definition -> it's a link, and it should take // preference, e.g. in `[^foo][bar]` if `[bar]` has a definition, `[^foo]` won't be a footnote reference. - return BracketResult.none(); + return LinkResult.none(); } var label = text.substring(1); @@ -34,12 +34,12 @@ public BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlinePar // Note that the definition parser already checked the syntax of the label, we don't need to check again. var def = context.getDefinition(FootnoteDefinition.class, label); if (def == null) { - return BracketResult.none(); + return LinkResult.none(); } // For footnotes, we only ever consume the text part of the link, not the label part (if any) - var position = bracketInfo.afterTextBracket(); + var position = linkInfo.afterTextBracket(); // If the marker is `![`, we don't want to include the `!`, so start from bracket - return BracketResult.replaceWith(new FootnoteReference(label), position).startFromBracket(); + return LinkResult.replaceWith(new FootnoteReference(label), position).startFromBracket(); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index cff40486b..0556626cf 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -6,7 +6,7 @@ import org.commonmark.parser.InlineParserFactory; import org.commonmark.parser.SourceLine; import org.commonmark.parser.SourceLines; -import org.commonmark.parser.beta.BracketProcessor; +import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.block.*; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -73,7 +73,7 @@ public class DocumentParser implements ParserState { private final InlineParserFactory inlineParserFactory; private final List inlineContentParserFactories; private final List delimiterProcessors; - private final List bracketProcessors; + private final List linkProcessors; private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; private final Definitions definitions = new Definitions(); @@ -83,12 +83,12 @@ public class DocumentParser implements ParserState { public DocumentParser(List blockParserFactories, InlineParserFactory inlineParserFactory, List inlineContentParserFactories, List delimiterProcessors, - List bracketProcessors, IncludeSourceSpans includeSourceSpans) { + List linkProcessors, IncludeSourceSpans includeSourceSpans) { this.blockParserFactories = blockParserFactories; this.inlineParserFactory = inlineParserFactory; this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; - this.bracketProcessors = bracketProcessors; + this.linkProcessors = linkProcessors; this.includeSourceSpans = includeSourceSpans; this.documentBlockParser = new DocumentBlockParser(); @@ -466,7 +466,7 @@ private BlockStartImpl findBlockStart(BlockParser blockParser) { * Walk through a block & children recursively, parsing string content into inline content where appropriate. */ private void processInlines() { - var context = new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, bracketProcessors, definitions); + var context = new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, linkProcessors, definitions); var inlineParser = inlineParserFactory.create(context); for (var blockParser : allBlockParsers) { diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java index 79fe2a56a..2f03b2052 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java @@ -2,7 +2,7 @@ import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParserContext; -import org.commonmark.parser.beta.BracketProcessor; +import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -12,16 +12,16 @@ public class InlineParserContextImpl implements InlineParserContext { private final List inlineContentParserFactories; private final List delimiterProcessors; - private final List bracketProcessors; + private final List linkProcessors; private final Definitions definitions; public InlineParserContextImpl(List inlineContentParserFactories, List delimiterProcessors, - List bracketProcessors, + List linkProcessors, Definitions definitions) { this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; - this.bracketProcessors = bracketProcessors; + this.linkProcessors = linkProcessors; this.definitions = definitions; } @@ -36,8 +36,8 @@ public List getCustomDelimiterProcessors() { } @Override - public List getCustomBracketProcessors() { - return bracketProcessors; + public List getCustomLinkProcessors() { + return linkProcessors; } @Override diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 22c8181b3..a252ed9f0 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -19,7 +19,7 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private final InlineParserContext context; private final List inlineContentParserFactories; private final Map delimiterProcessors; - private final List bracketProcessors; + private final List linkProcessors; private final BitSet specialCharacters; private Map> inlineParsers; @@ -42,7 +42,7 @@ public InlineParserImpl(InlineParserContext context) { this.context = context; this.inlineContentParserFactories = calculateInlineContentParserFactories(context.getCustomInlineContentParserFactories()); this.delimiterProcessors = calculateDelimiterProcessors(context.getCustomDelimiterProcessors()); - this.bracketProcessors = calculateBracketProcessors(context.getCustomBracketProcessors()); + this.linkProcessors = calculateLinkProcessors(context.getCustomLinkProcessors()); this.specialCharacters = calculateSpecialCharacters(this.delimiterProcessors.keySet(), this.inlineContentParserFactories); } @@ -57,10 +57,10 @@ private List calculateInlineContentParserFactories(L return list; } - private List calculateBracketProcessors(List bracketProcessors) { - // Custom bracket processors can override the built-in behavior, so make sure they are tried first - var list = new ArrayList<>(bracketProcessors); - list.add(new CoreBracketProcessor()); + private List calculateLinkProcessors(List linkProcessors) { + // Custom link processors can override the built-in behavior, so make sure they are tried first + var list = new ArrayList<>(linkProcessors); + list.add(new CoreLinkProcessor()); return list; } @@ -329,15 +329,15 @@ private Node parseLinkOrImage(Bracket opener, Position beforeClose) { } var processorStartPosition = scanner.position(); - for (var bracketProcessor : bracketProcessors) { - var bracketResult = bracketProcessor.process(bracketInfo, scanner, context); - if (!(bracketResult instanceof BracketResultImpl)) { + for (var linkProcessor : linkProcessors) { + var linkResult = linkProcessor.process(bracketInfo, scanner, context); + if (!(linkResult instanceof LinkResultImpl)) { // Reset position in case the processor used the scanner, and it didn't work out. scanner.setPosition(processorStartPosition); continue; } - var result = (BracketResultImpl) bracketResult; + var result = (LinkResultImpl) linkResult; var node = result.getNode(); var position = result.getPosition(); var startFromBracket = result.isStartFromBracket(); @@ -355,7 +355,7 @@ private Node parseLinkOrImage(Bracket opener, Position beforeClose) { return null; } - private BracketInfo parseBracketInfo(Bracket opener, Position beforeClose) { + private LinkInfo parseBracketInfo(Bracket opener, Position beforeClose) { // Check to see if we have a link (or image, with a ! in front). The different types: // - Inline: `[foo](/uri)` or with optional title `[foo](/uri "title")` // - Reference links @@ -363,7 +363,7 @@ private BracketInfo parseBracketInfo(Bracket opener, Position beforeClose) { // - Collapsed: `[foo][]` (foo is both the text and label) // - Shortcut: `[foo]` (foo is both the text and label) - var openerType = opener.image ? BracketInfo.OpenerType.IMAGE : BracketInfo.OpenerType.LINK; + var openerType = opener.image ? LinkInfo.OpenerType.IMAGE : LinkInfo.OpenerType.LINK; String text = scanner.getSource(opener.contentPosition, beforeClose).getContent(); // Starting position is after the closing `]` @@ -372,7 +372,7 @@ private BracketInfo parseBracketInfo(Bracket opener, Position beforeClose) { // Maybe an inline link/image var destinationTitle = parseInlineDestinationTitle(scanner); if (destinationTitle != null) { - return new BracketInfoImpl(openerType, null, text, null, destinationTitle.destination, destinationTitle.title, afterClose); + return new LinkInfoImpl(openerType, null, text, null, destinationTitle.destination, destinationTitle.title, afterClose); } // Not an inline link/image, rewind back to after `]`. scanner.setPosition(afterClose); @@ -388,17 +388,17 @@ private BracketInfo parseBracketInfo(Bracket opener, Position beforeClose) { scanner.setPosition(afterClose); } var referenceType = label == null ? - BracketInfo.ReferenceType.SHORTCUT : label.isEmpty() ? - BracketInfo.ReferenceType.COLLAPSED : - BracketInfo.ReferenceType.FULL; - if ((referenceType == BracketInfo.ReferenceType.SHORTCUT || referenceType == BracketInfo.ReferenceType.COLLAPSED) + LinkInfo.ReferenceType.SHORTCUT : label.isEmpty() ? + LinkInfo.ReferenceType.COLLAPSED : + LinkInfo.ReferenceType.FULL; + if ((referenceType == LinkInfo.ReferenceType.SHORTCUT || referenceType == LinkInfo.ReferenceType.COLLAPSED) && opener.bracketAfter) { // In case of SHORTCUT or COLLAPSED, the text is used as the reference. But the reference is not allowed to // contain an unescaped bracket, so if that's the case we don't need to continue. This is an optimization. return null; } - return new BracketInfoImpl(openerType, referenceType, text, label, null, null, afterClose); + return new LinkInfoImpl(openerType, referenceType, text, label, null, null, afterClose); } private Node wrapBracket(Bracket opener, Node wrapperNode, boolean startFromBracket) { @@ -880,7 +880,7 @@ public DestinationTitle(String destination, String title) { } } - private static class BracketInfoImpl implements BracketInfo { + private static class LinkInfoImpl implements LinkInfo { private final OpenerType openerType; private final ReferenceType referenceType; @@ -890,8 +890,8 @@ private static class BracketInfoImpl implements BracketInfo { private final String title; private final Position afterTextBracket; - private BracketInfoImpl(OpenerType openerType, ReferenceType referenceType, String text, String label, - String destination, String title, Position afterTextBracket) { + private LinkInfoImpl(OpenerType openerType, ReferenceType referenceType, String text, String label, + String destination, String title, Position afterTextBracket) { this.openerType = openerType; this.referenceType = referenceType; this.text = text; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java deleted file mode 100644 index 83040302b..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/inline/CoreBracketProcessor.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.commonmark.internal.inline; - -import org.commonmark.node.Image; -import org.commonmark.node.Link; -import org.commonmark.node.LinkReferenceDefinition; -import org.commonmark.node.Node; -import org.commonmark.parser.InlineParserContext; -import org.commonmark.parser.beta.BracketInfo; -import org.commonmark.parser.beta.BracketProcessor; -import org.commonmark.parser.beta.BracketResult; -import org.commonmark.parser.beta.Scanner; - -public class CoreBracketProcessor implements BracketProcessor { - - @Override - public BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlineParserContext context) { - if (bracketInfo.destination() != null) { - // Inline link - var node = createNode(bracketInfo, bracketInfo.destination(), bracketInfo.title()); - return BracketResult.wrapTextIn(node, scanner.position()); - } - - var label = bracketInfo.label(); - var ref = label != null && !label.isEmpty() ? label : bracketInfo.text(); - var def = context.getDefinition(LinkReferenceDefinition.class, ref); - if (def != null) { - // Reference link - var node = createNode(bracketInfo, def.getDestination(), def.getTitle()); - return BracketResult.wrapTextIn(node, scanner.position()); - } - return BracketResult.none(); - } - - private static Node createNode(BracketInfo bracketInfo, String destination, String title) { - return bracketInfo.openerType() == BracketInfo.OpenerType.IMAGE ? - new Image(destination, title) : - new Link(destination, title); - } -} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/CoreLinkProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/CoreLinkProcessor.java new file mode 100644 index 000000000..896b375da --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/CoreLinkProcessor.java @@ -0,0 +1,39 @@ +package org.commonmark.internal.inline; + +import org.commonmark.node.Image; +import org.commonmark.node.Link; +import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.node.Node; +import org.commonmark.parser.InlineParserContext; +import org.commonmark.parser.beta.LinkInfo; +import org.commonmark.parser.beta.LinkProcessor; +import org.commonmark.parser.beta.LinkResult; +import org.commonmark.parser.beta.Scanner; + +public class CoreLinkProcessor implements LinkProcessor { + + @Override + public LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContext context) { + if (linkInfo.destination() != null) { + // Inline link + var node = createNode(linkInfo, linkInfo.destination(), linkInfo.title()); + return LinkResult.wrapTextIn(node, scanner.position()); + } + + var label = linkInfo.label(); + var ref = label != null && !label.isEmpty() ? label : linkInfo.text(); + var def = context.getDefinition(LinkReferenceDefinition.class, ref); + if (def != null) { + // Reference link + var node = createNode(linkInfo, def.getDestination(), def.getTitle()); + return LinkResult.wrapTextIn(node, scanner.position()); + } + return LinkResult.none(); + } + + private static Node createNode(LinkInfo linkInfo, String destination, String title) { + return linkInfo.openerType() == LinkInfo.OpenerType.IMAGE ? + new Image(destination, title) : + new Link(destination, title); + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BracketResultImpl.java b/commonmark/src/main/java/org/commonmark/internal/inline/LinkResultImpl.java similarity index 76% rename from commonmark/src/main/java/org/commonmark/internal/inline/BracketResultImpl.java rename to commonmark/src/main/java/org/commonmark/internal/inline/LinkResultImpl.java index 5a0f18515..d51f2f4cf 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BracketResultImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/LinkResultImpl.java @@ -1,12 +1,12 @@ package org.commonmark.internal.inline; import org.commonmark.node.Node; -import org.commonmark.parser.beta.BracketResult; +import org.commonmark.parser.beta.LinkResult; import org.commonmark.parser.beta.Position; -public class BracketResultImpl implements BracketResult { +public class LinkResultImpl implements LinkResult { @Override - public BracketResult startFromBracket() { + public LinkResult startFromBracket() { startFromBracket = true; return this; } @@ -22,7 +22,7 @@ public enum Type { private boolean startFromBracket; - public BracketResultImpl(Type type, Node node, Position position) { + public LinkResultImpl(Type type, Node node, Position position) { this.type = type; this.node = node; this.position = position; diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java index 4a2951e70..1f428d22c 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -1,7 +1,7 @@ package org.commonmark.parser; import org.commonmark.node.LinkReferenceDefinition; -import org.commonmark.parser.beta.BracketProcessor; +import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -27,7 +27,7 @@ public interface InlineParserContext { /** * TODO */ - List getCustomBracketProcessors(); + List getCustomLinkProcessors(); /** * Look up a {@link LinkReferenceDefinition} for a given label. diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index e09d45da3..c65680a40 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -6,7 +6,7 @@ import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; import org.commonmark.node.*; -import org.commonmark.parser.beta.BracketProcessor; +import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.block.BlockParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -33,7 +33,7 @@ public class Parser { private final List blockParserFactories; private final List inlineContentParserFactories; private final List delimiterProcessors; - private final List bracketProcessors; + private final List linkProcessors; private final InlineParserFactory inlineParserFactory; private final List postProcessors; private final IncludeSourceSpans includeSourceSpans; @@ -44,13 +44,13 @@ private Parser(Builder builder) { this.postProcessors = builder.postProcessors; this.inlineContentParserFactories = builder.inlineContentParserFactories; this.delimiterProcessors = builder.delimiterProcessors; - this.bracketProcessors = builder.bracketProcessors; + this.linkProcessors = builder.linkProcessors; this.includeSourceSpans = builder.includeSourceSpans; // Try to construct an inline parser. Invalid configuration might result in an exception, which we want to // detect as soon as possible. var context = new InlineParserContextImpl( - inlineContentParserFactories, delimiterProcessors, bracketProcessors, new Definitions()); + inlineContentParserFactories, delimiterProcessors, linkProcessors, new Definitions()); this.inlineParserFactory.create(context); } @@ -105,7 +105,7 @@ public Node parseReader(Reader input) throws IOException { private DocumentParser createDocumentParser() { return new DocumentParser(blockParserFactories, inlineParserFactory, inlineContentParserFactories, - delimiterProcessors, bracketProcessors, includeSourceSpans); + delimiterProcessors, linkProcessors, includeSourceSpans); } private Node postProcess(Node document) { @@ -122,7 +122,7 @@ public static class Builder { private final List blockParserFactories = new ArrayList<>(); private final List inlineContentParserFactories = new ArrayList<>(); private final List delimiterProcessors = new ArrayList<>(); - private final List bracketProcessors = new ArrayList<>(); + private final List linkProcessors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); private InlineParserFactory inlineParserFactory; @@ -250,9 +250,9 @@ public Builder customDelimiterProcessor(DelimiterProcessor delimiterProcessor) { /** * TODO */ - public Builder bracketProcessor(BracketProcessor bracketProcessor) { - Objects.requireNonNull(bracketProcessor, "bracketProcessor must not be null"); - bracketProcessors.add(bracketProcessor); + public Builder linkProcessor(LinkProcessor linkProcessor) { + Objects.requireNonNull(linkProcessor, "linkProcessor must not be null"); + linkProcessors.add(linkProcessor); return this; } diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/BracketProcessor.java b/commonmark/src/main/java/org/commonmark/parser/beta/BracketProcessor.java deleted file mode 100644 index 5d7aae236..000000000 --- a/commonmark/src/main/java/org/commonmark/parser/beta/BracketProcessor.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.commonmark.parser.beta; - -import org.commonmark.parser.InlineParserContext; - -public interface BracketProcessor { - - BracketResult process(BracketInfo bracketInfo, Scanner scanner, InlineParserContext context); -} diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/BracketResult.java b/commonmark/src/main/java/org/commonmark/parser/beta/BracketResult.java deleted file mode 100644 index e9571391b..000000000 --- a/commonmark/src/main/java/org/commonmark/parser/beta/BracketResult.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.commonmark.parser.beta; - -import org.commonmark.internal.inline.BracketResultImpl; -import org.commonmark.node.Node; - -public interface BracketResult { - static BracketResult none() { - return null; - } - - static BracketResult wrapTextIn(Node node, Position position) { - return new BracketResultImpl(BracketResultImpl.Type.WRAP, node, position); - } - - static BracketResult replaceWith(Node node, Position position) { - return new BracketResultImpl(BracketResultImpl.Type.REPLACE, node, position); - } - - BracketResult startFromBracket(); -} diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java similarity index 96% rename from commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java rename to commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java index cdeea92cc..7dea28d96 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/BracketInfo.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java @@ -1,6 +1,6 @@ package org.commonmark.parser.beta; -public interface BracketInfo { +public interface LinkInfo { enum OpenerType { // An image (a `!` before the `[`) IMAGE, diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java new file mode 100644 index 000000000..6d5a1cfaf --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java @@ -0,0 +1,8 @@ +package org.commonmark.parser.beta; + +import org.commonmark.parser.InlineParserContext; + +public interface LinkProcessor { + + LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContext context); +} diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java new file mode 100644 index 000000000..69a3bebc5 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java @@ -0,0 +1,20 @@ +package org.commonmark.parser.beta; + +import org.commonmark.internal.inline.LinkResultImpl; +import org.commonmark.node.Node; + +public interface LinkResult { + static LinkResult none() { + return null; + } + + static LinkResult wrapTextIn(Node node, Position position) { + return new LinkResultImpl(LinkResultImpl.Type.WRAP, node, position); + } + + static LinkResult replaceWith(Node node, Position position) { + return new LinkResultImpl(LinkResultImpl.Type.REPLACE, node, position); + } + + LinkResult startFromBracket(); +} diff --git a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java index 07b94c076..264eb35d6 100644 --- a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java +++ b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java @@ -1,7 +1,7 @@ package org.commonmark.test; import org.commonmark.internal.InlineParserImpl; -import org.commonmark.parser.beta.BracketProcessor; +import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParser; @@ -53,8 +53,8 @@ public List getCustomDelimiterProcessors() { } @Override - public List getCustomBracketProcessors() { - return inlineParserContext.getCustomBracketProcessors(); + public List getCustomLinkProcessors() { + return inlineParserContext.getCustomLinkProcessors(); } @Override From a2258faba36836c97c862e96988ad9d2f7fe9cab Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 29 Jun 2024 15:03:24 +1000 Subject: [PATCH 127/345] Remove unused ReferenceType, add docs --- .../commonmark/internal/InlineParserImpl.java | 31 +++++---------- .../org/commonmark/parser/beta/LinkInfo.java | 39 ++++++++++++++----- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index a252ed9f0..bf6ac7b62 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -323,14 +323,14 @@ private Node parseCloseBracket() { } private Node parseLinkOrImage(Bracket opener, Position beforeClose) { - var bracketInfo = parseBracketInfo(opener, beforeClose); - if (bracketInfo == null) { + var linkInfo = parseLinkInfo(opener, beforeClose); + if (linkInfo == null) { return null; } var processorStartPosition = scanner.position(); for (var linkProcessor : linkProcessors) { - var linkResult = linkProcessor.process(bracketInfo, scanner, context); + var linkResult = linkProcessor.process(linkInfo, scanner, context); if (!(linkResult instanceof LinkResultImpl)) { // Reset position in case the processor used the scanner, and it didn't work out. scanner.setPosition(processorStartPosition); @@ -355,7 +355,7 @@ private Node parseLinkOrImage(Bracket opener, Position beforeClose) { return null; } - private LinkInfo parseBracketInfo(Bracket opener, Position beforeClose) { + private LinkInfo parseLinkInfo(Bracket opener, Position beforeClose) { // Check to see if we have a link (or image, with a ! in front). The different types: // - Inline: `[foo](/uri)` or with optional title `[foo](/uri "title")` // - Reference links @@ -372,7 +372,7 @@ private LinkInfo parseBracketInfo(Bracket opener, Position beforeClose) { // Maybe an inline link/image var destinationTitle = parseInlineDestinationTitle(scanner); if (destinationTitle != null) { - return new LinkInfoImpl(openerType, null, text, null, destinationTitle.destination, destinationTitle.title, afterClose); + return new LinkInfoImpl(openerType, text, null, destinationTitle.destination, destinationTitle.title, afterClose); } // Not an inline link/image, rewind back to after `]`. scanner.setPosition(afterClose); @@ -387,18 +387,14 @@ private LinkInfo parseBracketInfo(Bracket opener, Position beforeClose) { // No label, rewind back scanner.setPosition(afterClose); } - var referenceType = label == null ? - LinkInfo.ReferenceType.SHORTCUT : label.isEmpty() ? - LinkInfo.ReferenceType.COLLAPSED : - LinkInfo.ReferenceType.FULL; - if ((referenceType == LinkInfo.ReferenceType.SHORTCUT || referenceType == LinkInfo.ReferenceType.COLLAPSED) - && opener.bracketAfter) { - // In case of SHORTCUT or COLLAPSED, the text is used as the reference. But the reference is not allowed to + var textIsReference = label == null || label.isEmpty(); + if (opener.bracketAfter && textIsReference) { + // In case of shortcut or collapsed links, the text is used as the reference. But the reference is not allowed to // contain an unescaped bracket, so if that's the case we don't need to continue. This is an optimization. return null; } - return new LinkInfoImpl(openerType, referenceType, text, label, null, null, afterClose); + return new LinkInfoImpl(openerType, text, label, null, null, afterClose); } private Node wrapBracket(Bracket opener, Node wrapperNode, boolean startFromBracket) { @@ -883,17 +879,15 @@ public DestinationTitle(String destination, String title) { private static class LinkInfoImpl implements LinkInfo { private final OpenerType openerType; - private final ReferenceType referenceType; private final String text; private final String label; private final String destination; private final String title; private final Position afterTextBracket; - private LinkInfoImpl(OpenerType openerType, ReferenceType referenceType, String text, String label, + private LinkInfoImpl(OpenerType openerType, String text, String label, String destination, String title, Position afterTextBracket) { this.openerType = openerType; - this.referenceType = referenceType; this.text = text; this.label = label; this.destination = destination; @@ -906,11 +900,6 @@ public OpenerType openerType() { return openerType; } - @Override - public ReferenceType referenceType() { - return referenceType; - } - @Override public String text() { return text; diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java index 7dea28d96..47dd8a9ea 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java @@ -1,5 +1,27 @@ package org.commonmark.parser.beta; +/** + * A parsed link or image. There are different types of links. + *

          + * Inline links: + *

          + * [text](destination)
          + * [text](destination "title")
          + * 
          + *

          + * Reference links, which have different subtypes. Full:: + *

          + * [text][label]
          + * 
          + * Collapsed (label is ""): + *
          + * [text][]
          + * 
          + * Shortcut (label is null): + *
          + * [text]
          + * 
          + */ public interface LinkInfo { enum OpenerType { // An image (a `!` before the `[`) @@ -8,24 +30,16 @@ enum OpenerType { LINK } - enum ReferenceType { - FULL, - COLLAPSED, - SHORTCUT - } - // TODO: We could also expose the opener Text (`[` or `![`) OpenerType openerType(); - ReferenceType referenceType(); - /** * The text between the first brackets, e.g. `foo` in `[foo][bar]`. */ String text(); /** - * The label, or null for shortcut links (in which case {@link #text()} should be used as the label). + * The label, or null for inline links or for shortcut links (in which case {@link #text()} should be used as the label). */ String label(); @@ -39,5 +53,12 @@ enum ReferenceType { */ String title(); + /** + * The position after the text bracket, e.g.: + *
          +     * [foo][bar]
          +     *      ^
          +     * 
          + */ Position afterTextBracket(); } From f028716e716ebecad518a3c14db3f0ecdd58e6af Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 30 Jun 2024 21:54:14 +1000 Subject: [PATCH 128/345] Skip spaces after colon in definition --- .../footnotes/internal/FootnoteBlockParser.java | 5 ++++- .../commonmark/ext/footnotes/FootnotesTest.java | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java index dabaf599d..f3a9dc73f 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java @@ -4,6 +4,7 @@ import org.commonmark.node.Block; import org.commonmark.node.DefinitionMap; import org.commonmark.parser.block.*; +import org.commonmark.text.Characters; import java.util.List; @@ -76,7 +77,9 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar case ']': if (index > labelStart && index + 1 < content.length() && content.charAt(index + 1) == ':') { var label = content.subSequence(labelStart, index).toString(); - return BlockStart.of(new FootnoteBlockParser(label)).atIndex(index + 2); + // After the colon, any number of spaces is skipped (not part of the content) + var afterSpaces = Characters.skipSpaceTab(content, index + 2, content.length()); + return BlockStart.of(new FootnoteBlockParser(label)).atIndex(afterSpaces); } else { return BlockStart.none(); } diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index 73e659aa7..fd2dcd43d 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -77,6 +77,22 @@ public void testDefContainsParagraph() { assertText("footnote", paragraph.getFirstChild()); } + @Test + public void testDefBlockStartSpacesAfterColon() { + var doc = PARSER.parse("[^1]: footnote\n"); + var def = find(doc, FootnoteDefinition.class); + var paragraph = (Paragraph) def.getFirstChild(); + assertText("footnote", paragraph.getFirstChild()); + } + + @Test + public void testDefContainsIndentedCodeBlock() { + var doc = PARSER.parse("[^1]:\n code\n"); + var def = find(doc, FootnoteDefinition.class); + var codeBlock = (IndentedCodeBlock) def.getFirstChild(); + assertEquals("code\n", codeBlock.getLiteral()); + } + @Test public void testDefContainsMultipleLines() { var doc = PARSER.parse("[^1]: footnote\nstill\n"); From 804e83c73ba8fdc0dd7294a45938ba96c63bf480 Mon Sep 17 00:00:00 2001 From: Andy Zhang <37402126+AnzhiZhang@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:18:36 +0100 Subject: [PATCH 129/345] Update tests for DefaultUrlSanitizer --- .../org/commonmark/test/HtmlRendererTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 7aec21ceb..7cc0b036a 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -93,6 +93,34 @@ public void sanitizedUrlsShouldSetRelNoFollow() { assertEquals("

          \n", sanitizeUrlsRenderer().render(paragraph)); } + @Test + public void sanitizedUrlsShouldAllowSafeProtocols() { + Paragraph paragraph = new Paragraph(); + Link link = new Link(); + link.setDestination("http://google.com"); + paragraph.appendChild(link); + assertEquals("

          \n", sanitizeUrlsRenderer().render(paragraph)); + + paragraph = new Paragraph(); + link = new Link(); + link.setDestination("https://google.com"); + paragraph.appendChild(link); + assertEquals("

          \n", sanitizeUrlsRenderer().render(paragraph)); + + paragraph = new Paragraph(); + link = new Link(); + link.setDestination("mailto:foo@bar.example.com"); + paragraph.appendChild(link); + assertEquals("

          \n", sanitizeUrlsRenderer().render(paragraph)); + + String image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAAAAQSURBVBhXY/iPBVBf8P9/AG8TY51nJdgkAAAAAElFTkSuQmCC"; + paragraph = new Paragraph(); + link = new Link(); + link.setDestination(image); + paragraph.appendChild(link); + assertEquals("

          \n", sanitizeUrlsRenderer().render(paragraph)); + } + @Test public void sanitizedUrlsShouldFilterDangerousProtocols() { Paragraph paragraph = new Paragraph(); @@ -100,6 +128,12 @@ public void sanitizedUrlsShouldFilterDangerousProtocols() { link.setDestination("javascript:alert(5);"); paragraph.appendChild(link); assertEquals("

          \n", sanitizeUrlsRenderer().render(paragraph)); + + paragraph = new Paragraph(); + link = new Link(); + link.setDestination("ftp://google.com"); + paragraph.appendChild(link); + assertEquals("

          \n", sanitizeUrlsRenderer().render(paragraph)); } @Test From 3388891e196bcecab8cff943c7c8b33790f0e5f0 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 3 Jul 2024 22:18:16 +1000 Subject: [PATCH 130/345] Support nested footnotes This turned out to be tricky, and GitHub gets some of it wrong. If anyone ever wants us to be bug-compatible, it should be relatively straightforward to emulate GitHub by just running the initial reference search over everything (including definitions) and then not bothering with finding more at the end. --- .../internal/FootnoteHtmlNodeRenderer.java | 162 ++++++++++++++---- .../footnotes/FootnoteHtmlRendererTest.java | 122 +++++++++++++ 2 files changed, 250 insertions(+), 34 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java index 17a46af0a..7146281ce 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java @@ -8,6 +8,7 @@ import org.commonmark.renderer.html.HtmlWriter; import java.util.*; +import java.util.function.Consumer; /** * HTML rendering for footnotes. @@ -38,6 +39,11 @@ public class FootnoteHtmlNodeRenderer implements NodeRenderer { */ private final Map referencedDefinitions = new LinkedHashMap<>(); + /** + * Information about references that should be rendered as footnotes. + */ + private final Map references = new HashMap<>(); + public FootnoteHtmlNodeRenderer(HtmlNodeRendererContext context) { this.html = context.getWriter(); this.context = context; @@ -51,23 +57,69 @@ public Set> getNodeTypes() { @Override public void beforeRoot(Node node) { // Collect definitions so we can look them up when encountering a reference later - var visitor = new FootnotesVisitor(); + var visitor = new DefinitionVisitor(); node.accept(visitor); definitionMap = visitor.definitions; + + // Register references in the main text. References inside definitions will be done later. + node.accept(new ReferenceVisitor(false, this::registerReference)); } @Override public void render(Node node) { if (node instanceof FootnoteReference) { - renderReference((FootnoteReference) node); + var ref = (FootnoteReference) node; + // This is called for all references, even ones inside definitions that we render at the end. + renderReference(ref, references.get(ref)); + } + } + + @Override + public void afterRoot(Node node) { + // Now render the referenced definitions if there are any. + if (referencedDefinitions.isEmpty()) { + return; + } + + var firstDef = referencedDefinitions.keySet().iterator().next(); + var attrs = new LinkedHashMap(); + attrs.put("class", "footnotes"); + attrs.put("data-footnotes", null); + html.tag("section", context.extendAttributes(firstDef, "section", attrs)); + html.line(); + html.tag("ol"); + html.line(); + + // Check whether there are any footnotes inside the definitions that we're about to render. For those, we might + // need to render more definitions. So do a breadth-first search to find all relevant definition. + var check = new LinkedList<>(referencedDefinitions.keySet()); + while (!check.isEmpty()) { + var def = check.removeFirst(); + def.accept(new ReferenceVisitor(true, ref -> { + var d = definitionMap.get(ref.getLabel()); + if (d != null) { + if (!referencedDefinitions.containsKey(d)) { + check.addLast(d); + } + registerReference(ref); + } + })); } + + for (var entry : referencedDefinitions.entrySet()) { + // This will also render any footnote references inside definitions + renderDefinition(entry.getKey(), entry.getValue()); + } + + html.tag("/ol"); + html.line(); + html.tag("/section"); + html.line(); } - private void renderReference(FootnoteReference ref) { + private void registerReference(FootnoteReference ref) { var def = definitionMap.get(ref.getLabel()); if (def == null) { - // A reference without a corresponding definition is rendered as plain text - html.text("[^" + ref.getLabel() + "]"); return; } @@ -81,43 +133,31 @@ private void renderReference(FootnoteReference ref) { var id = referenceId(def.getLabel(), refNumber); referencedDef.references.add(id); + var definitionId = definitionId(def.getLabel()); + + references.put(ref, new ReferenceInfo(id, definitionId, definitionNumber)); + } + + private void renderReference(FootnoteReference ref, ReferenceInfo referenceInfo) { + if (referenceInfo == null) { + // A reference without a corresponding definition is rendered as plain text + html.text("[^" + ref.getLabel() + "]"); + return; + } + html.tag("sup", context.extendAttributes(ref, "sup", Map.of("class", "footnote-ref"))); - var href = "#" + definitionId(def.getLabel()); + var href = "#" + referenceInfo.definitionId; var attrs = new LinkedHashMap(); attrs.put("href", href); - attrs.put("id", id); + attrs.put("id", referenceInfo.id); attrs.put("data-footnote-ref", null); html.tag("a", context.extendAttributes(ref, "a", attrs)); - html.raw(String.valueOf(definitionNumber)); + html.raw(String.valueOf(referenceInfo.definitionNumber)); html.tag("/a"); html.tag("/sup"); } - @Override - public void afterRoot(Node node) { - // Now render the referenced definitions if there are any - if (referencedDefinitions.isEmpty()) { - return; - } - - var firstDef = referencedDefinitions.keySet().iterator().next(); - var attrs = new LinkedHashMap(); - attrs.put("class", "footnotes"); - attrs.put("data-footnotes", null); - html.tag("section", context.extendAttributes(firstDef, "section", attrs)); - html.line(); - html.tag("ol"); - html.line(); - for (var entry : referencedDefinitions.entrySet()) { - renderDefinition(entry.getKey(), entry.getValue()); - } - html.tag("/ol"); - html.line(); - html.tag("/section"); - html.line(); - } - private void renderDefinition(FootnoteDefinition def, ReferencedDefinition referencedDefinition) { //
            etc var id = definitionId(def.getLabel()); @@ -127,7 +167,7 @@ private void renderDefinition(FootnoteDefinition def, ReferencedDefinition refer html.line(); if (def.getLastChild() instanceof Paragraph) { - // Add backlinks into last paragraph before "p". This is what GFM does. + // Add backlinks into last paragraph before

            . This is what GFM does. var lastParagraph = (Paragraph) def.getLastChild(); var node = def.getFirstChild(); while (node != lastParagraph) { @@ -205,7 +245,7 @@ private void renderChildren(Node parent) { } } - private static class FootnotesVisitor extends AbstractVisitor { + private static class DefinitionVisitor extends AbstractVisitor { private final DefinitionMap definitions = new DefinitionMap<>(FootnoteDefinition.class); @@ -214,6 +254,39 @@ public void visit(CustomBlock customBlock) { if (customBlock instanceof FootnoteDefinition) { var def = (FootnoteDefinition) customBlock; definitions.putIfAbsent(def.getLabel(), def); + } else { + super.visit(customBlock); + } + } + } + + private static class ReferenceVisitor extends AbstractVisitor { + private final boolean inspectDefinitions; + private final Consumer consumer; + + private ReferenceVisitor(boolean inspectDefinitions, Consumer consumer) { + this.inspectDefinitions = inspectDefinitions; + this.consumer = consumer; + } + + @Override + public void visit(CustomNode customNode) { + if (customNode instanceof FootnoteReference) { + var ref = (FootnoteReference) customNode; + consumer.accept(ref); + } else { + super.visit(customNode); + } + } + + @Override + public void visit(CustomBlock customBlock) { + if (customBlock instanceof FootnoteDefinition) { + if (inspectDefinitions) { + super.visit(customBlock); + } + } else { + super.visit(customBlock); } } } @@ -232,4 +305,25 @@ private static class ReferencedDefinition { this.definitionNumber = definitionNumber; } } + + private static class ReferenceInfo { + /** + * The ID of the reference; in the corresponding definition, a link back to this reference will be rendered. + */ + private final String id; + /** + * The ID of the definition, for linking to the definition. + */ + private final String definitionId; + /** + * The definition number, rendered in superscript. + */ + private final int definitionNumber; + + private ReferenceInfo(String id, String definitionId, int definitionNumber) { + this.id = id; + this.definitionId = definitionId; + this.definitionNumber = definitionNumber; + } + } } diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java index b47e0ccba..f9746d13c 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java @@ -98,6 +98,128 @@ public void testDefinitionWithList() { "\n"); } + // Text in footnote definitions can reference other footnotes, even ones that aren't referenced in the main text. + // This makes them tricky because it's not enough to just go through the main text for references. + // And before we can render a definition, we need to know all references (because we add links back to references). + // + // In other words, footnotes form a directed graph. Footnotes can reference each other so cycles are possible too. + // + // One way to implement it, which is what cmark-gfm does, is to go through the whole document (including definitions) + // and find all references in order. That guarantees that all definitions are found, but it has strange results for + // ordering or when the reference is in an unreferenced definition, see tests below. + // In graph terms, it renders all definitions that have an incoming edge, no matter whether they are connected to + // the main text or not. + // + // The way we implement it is by starting with the footnotes from the main text. Then from the referenced + // definitions, run a breadth-first search to find all remaining relevant footnote definitions. + + @Test + public void testNestedFootnotesSimple() { + assertRendering("[^foo1]\n" + + "\n" + + "[^foo1]: one [^foo2]\n" + + "[^foo2]: two\n", "

            1

            \n" + + "
            \n" + + "
              \n" + + "
            1. \n" + + "

              one 2

              \n" + + "
            2. \n" + + "
            3. \n" + + "

              two

              \n" + + "
            4. \n" + + "
            \n" + + "
            \n"); + } + + @Test + public void testNestedFootnotesOrder() { + // GitHub has a strange result here, the definitions are in order: 1. bar, 2. foo. + // The reason is that the number is done based on all references in document order, including references in + // definitions. So [^bar] from the first line is first. + assertRendering("[^foo]: foo [^bar]\n" + + "\n" + + "[^foo]\n" + + "\n" + + "[^bar]: bar\n", "

            1

            \n" + + "
            \n" + + "
              \n" + + "
            1. \n" + + "

              foo 2

              \n" + + "
            2. \n" + + "
            3. \n" + + "

              bar

              \n" + + "
            4. \n" + + "
            \n" + + "
            \n"); + } + + @Test + public void testNestedFootnotesOrder2() { + assertRendering("[^1]\n" + + "\n" + + "[^4]: four\n" + + "[^3]: three [^4]\n" + + "[^2]: two [^4]\n" + + "[^1]: one [^2][^3]\n", "

            1

            \n" + + "
            \n" + + "
              \n" + + "
            1. \n" + + "

              one 23

              \n" + + "
            2. \n" + + "
            3. \n" + + "

              two 4

              \n" + + "
            4. \n" + + "
            5. \n" + + "

              three 4

              \n" + + "
            6. \n" + + "
            7. \n" + + "

              four 2

              \n" + + "
            8. \n" + + "
            \n" + + "
            \n"); + } + + @Test + public void testNestedFootnotesCycle() { + // Footnotes can contain cycles, lol. + assertRendering("[^foo1]\n" + + "\n" + + "[^foo1]: one [^foo2]\n" + + "[^foo2]: two [^foo1]\n", "

            1

            \n" + + "
            \n" + + "
              \n" + + "
            1. \n" + + "

              one 2 2

              \n" + + "
            2. \n" + + "
            3. \n" + + "

              two 1

              \n" + + "
            4. \n" + + "
            \n" + + "
            \n"); + } + + @Test + public void testNestedFootnotesUnreferenced() { + // This should not result in any footnotes, as baz itself isn't referenced. + // But GitHub renders bar only, with a broken backref, because bar is referenced from foo. + assertRendering("[^foo]: foo[^bar]\n" + + "[^bar]: bar\n", ""); + + // And here only 1 is rendered. + assertRendering("[^1]\n" + + "\n" + + "[^1]: one\n" + + "[^foo]: foo[^bar]\n" + + "[^bar]: bar\n", "

            1

            \n" + + "
            \n" + + "
              \n" + + "
            1. \n" + + "

              one

              \n" + + "
            2. \n" + + "
            \n" + + "
            \n"); + } + @Test public void testRenderNodesDirectly() { // Everything should work as expected when rendering from nodes directly (no parsing step). From 5425f632d071e4b0a23496c1ca060a6a3b5d1212 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 5 Jul 2024 22:36:56 +1000 Subject: [PATCH 131/345] Change footnote visiting, move docs to class --- .../internal/FootnoteHtmlNodeRenderer.java | 62 +++++++++++-------- .../footnotes/FootnoteHtmlRendererTest.java | 15 +---- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java index 7146281ce..dbae3ab90 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java @@ -23,6 +23,29 @@ *
          1. Definitions are ordered by number
          2. *
          3. Definitions have links back to their references (one or more)
          4. *
    + * + *

    Nested footnotes

    + * Text in footnote definitions can reference other footnotes, even ones that aren't referenced in the main text. + * This makes them tricky because it's not enough to just go through the main text for references. + * And before we can render a definition, we need to know all references (because we add links back to references). + *

    + * In other words, footnotes form a directed graph. Footnotes can reference each other so cycles are possible too. + *

    + * One way to implement it, which is what cmark-gfm does, is to go through the whole document (including definitions) + * and find all references in order. That guarantees that all definitions are found, but it has strange results for + * ordering or when the reference is in an unreferenced definition, see tests. In graph terms, it renders all + * definitions that have an incoming edge, no matter whether they are connected to the main text or not. + *

    + * The way we implement it: + *

      + *
    1. Start with the references in the main text; we can render them as we go
    2. + *
    3. After the main text is rendered, we have the referenced definitions, but there might be more from definition text
    4. + *
    5. To find the remaining definitions, we visit the definitions from before to look at references
    6. + *
    7. Repeat (breadth-first search) until we've found all definitions (note that we can't render before that's done because of backrefs)
    8. + *
    9. Now render the definitions (and any references inside)
    10. + *
    + * This means we only render definitions whose references are actually rendered, and in a meaningful order (all main + * text footnotes first, then any nested ones). */ public class FootnoteHtmlNodeRenderer implements NodeRenderer { @@ -40,7 +63,8 @@ public class FootnoteHtmlNodeRenderer implements NodeRenderer { private final Map referencedDefinitions = new LinkedHashMap<>(); /** - * Information about references that should be rendered as footnotes. + * Information about references that should be rendered as footnotes. This doesn't contain all references, just the + * ones from inside definitions. */ private final Map references = new HashMap<>(); @@ -56,21 +80,20 @@ public Set> getNodeTypes() { @Override public void beforeRoot(Node node) { - // Collect definitions so we can look them up when encountering a reference later + // Collect all definitions first, so we can look them up when encountering a reference later. var visitor = new DefinitionVisitor(); node.accept(visitor); definitionMap = visitor.definitions; - - // Register references in the main text. References inside definitions will be done later. - node.accept(new ReferenceVisitor(false, this::registerReference)); } @Override public void render(Node node) { if (node instanceof FootnoteReference) { - var ref = (FootnoteReference) node; // This is called for all references, even ones inside definitions that we render at the end. - renderReference(ref, references.get(ref)); + var ref = (FootnoteReference) node; + // Use containsKey because if value is null, we don't need to try registering again. + var info = references.containsKey(ref) ? references.get(ref) : registerReference(ref); + renderReference(ref, info); } } @@ -95,13 +118,13 @@ public void afterRoot(Node node) { var check = new LinkedList<>(referencedDefinitions.keySet()); while (!check.isEmpty()) { var def = check.removeFirst(); - def.accept(new ReferenceVisitor(true, ref -> { + def.accept(new ReferenceVisitor(ref -> { var d = definitionMap.get(ref.getLabel()); if (d != null) { if (!referencedDefinitions.containsKey(d)) { check.addLast(d); } - registerReference(ref); + references.put(ref, registerReference(ref)); } })); } @@ -117,10 +140,10 @@ public void afterRoot(Node node) { html.line(); } - private void registerReference(FootnoteReference ref) { + private ReferenceInfo registerReference(FootnoteReference ref) { var def = definitionMap.get(ref.getLabel()); if (def == null) { - return; + return null; } // The first referenced definition gets number 1, second one 2, etc. @@ -135,7 +158,7 @@ private void registerReference(FootnoteReference ref) { var definitionId = definitionId(def.getLabel()); - references.put(ref, new ReferenceInfo(id, definitionId, definitionNumber)); + return new ReferenceInfo(id, definitionId, definitionNumber); } private void renderReference(FootnoteReference ref, ReferenceInfo referenceInfo) { @@ -261,11 +284,9 @@ public void visit(CustomBlock customBlock) { } private static class ReferenceVisitor extends AbstractVisitor { - private final boolean inspectDefinitions; private final Consumer consumer; - private ReferenceVisitor(boolean inspectDefinitions, Consumer consumer) { - this.inspectDefinitions = inspectDefinitions; + private ReferenceVisitor(Consumer consumer) { this.consumer = consumer; } @@ -278,17 +299,6 @@ public void visit(CustomNode customNode) { super.visit(customNode); } } - - @Override - public void visit(CustomBlock customBlock) { - if (customBlock instanceof FootnoteDefinition) { - if (inspectDefinitions) { - super.visit(customBlock); - } - } else { - super.visit(customBlock); - } - } } private static class ReferencedDefinition { diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java index f9746d13c..8964e536c 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java @@ -98,20 +98,7 @@ public void testDefinitionWithList() { "\n"); } - // Text in footnote definitions can reference other footnotes, even ones that aren't referenced in the main text. - // This makes them tricky because it's not enough to just go through the main text for references. - // And before we can render a definition, we need to know all references (because we add links back to references). - // - // In other words, footnotes form a directed graph. Footnotes can reference each other so cycles are possible too. - // - // One way to implement it, which is what cmark-gfm does, is to go through the whole document (including definitions) - // and find all references in order. That guarantees that all definitions are found, but it has strange results for - // ordering or when the reference is in an unreferenced definition, see tests below. - // In graph terms, it renders all definitions that have an incoming edge, no matter whether they are connected to - // the main text or not. - // - // The way we implement it is by starting with the footnotes from the main text. Then from the referenced - // definitions, run a breadth-first search to find all remaining relevant footnote definitions. + // See docs on FootnoteHtmlNodeRenderer about nested footnotes. @Test public void testNestedFootnotesSimple() { From 958048deadc3d2759440baaafdd48c8e12df7c79 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 6 Jul 2024 21:56:07 +1000 Subject: [PATCH 132/345] Adjust Markdown renderer to changed parsing --- .../ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java index 956cd1de1..11b315386 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java @@ -48,10 +48,7 @@ private void renderReference(FootnoteReference ref) { private void renderDefinition(FootnoteDefinition def) { writer.raw("[^"); writer.raw(def.getLabel()); - writer.raw("]:"); - if (def.getFirstChild() instanceof Paragraph) { - writer.raw(" "); - } + writer.raw("]: "); writer.pushPrefix(" "); writer.pushTight(true); From 0a8c9937cc73438a2a11383a8a8aae7000e66d54 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 6 Jul 2024 22:04:29 +1000 Subject: [PATCH 133/345] Add docs for footnotes extension --- .../ext/footnotes/FootnoteDefinition.java | 10 ++++++++++ .../ext/footnotes/FootnoteReference.java | 6 ++++++ .../ext/footnotes/FootnotesExtension.java | 18 +++++++++++++++++- .../internal/FootnoteBlockParser.java | 3 +++ .../internal/FootnoteLinkProcessor.java | 3 +++ 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteDefinition.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteDefinition.java index 4adf98462..4a560dc9e 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteDefinition.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteDefinition.java @@ -2,6 +2,16 @@ import org.commonmark.node.CustomBlock; +/** + * A footnote definition, e.g.: + *
    
    + * [^foo]: This is the footnote text
    + * 
    + * The {@link #getLabel() label} is the text in brackets after {@code ^}, so {@code foo} in the example. The contents + * of the footnote are child nodes of the definition, a {@link org.commonmark.node.Paragraph} in the example. + *

    + * Footnote definitions are parsed even if there's no corresponding {@link FootnoteReference}. + */ public class FootnoteDefinition extends CustomBlock { private String label; diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteReference.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteReference.java index d7eda870c..61dcf8626 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteReference.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteReference.java @@ -2,6 +2,12 @@ import org.commonmark.node.CustomNode; +/** + * A footnote reference, e.g. [^foo] in Some text with a footnote[^foo] + *

    + * The {@link #getLabel() label} is the text within brackets after {@code ^}, so {@code foo} in the example. It needs to + * match the label of a corresponding {@link FootnoteDefinition} for the footnote to be parsed. + */ public class FootnoteReference extends CustomNode { private String label; diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java index 263b52b9c..9ac2c506e 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java @@ -15,7 +15,23 @@ import java.util.Set; /** - * TODO + * Extension for footnotes with syntax like GitHub Flavored Markdown: + *

    
    + * Some text with a footnote[^1].
    + *
    + * [^1]: The text of the footnote.
    + * 
    + * The [^1] is a {@link FootnoteReference}, with "1" being the label. + *

    + * The line with [^1]: ... is a {@link FootnoteDefinition}, with the contents as child nodes (can be a + * paragraph like in the example, or other blocks like lists). + *

    + * All the footnotes (definitions) will be rendered in a list at the end of a document, no matter where they appear in + * the source. The footnotes will be numbered starting from 1, then 2, etc, depending on the order in which they appear + * in the text (and not dependent on the label). The footnote reference is a link to the footnote, and from the footnote + * there is a link back to the reference (or multiple). + * + * @see GitHub docs for footnotes */ public class FootnotesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java index f3a9dc73f..59ee5529e 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java @@ -8,6 +8,9 @@ import java.util.List; +/** + * Parser for a single {@link FootnoteDefinition} block. + */ public class FootnoteBlockParser extends AbstractBlockParser { private final FootnoteDefinition block; diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java index 1af57893f..077ca5b70 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java @@ -9,6 +9,9 @@ import org.commonmark.parser.beta.LinkResult; import org.commonmark.parser.beta.Scanner; +/** + * For turning e.g. [^foo] into a {@link FootnoteReference}. + */ public class FootnoteLinkProcessor implements LinkProcessor { @Override public LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContext context) { From 214c195ff4cd8ea297bcf50672f286731d253a9a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 6 Jul 2024 23:05:17 +1000 Subject: [PATCH 134/345] Add docs for LinkProcessor --- .../parser/InlineParserContext.java | 2 +- .../java/org/commonmark/parser/Parser.java | 8 ++++- .../org/commonmark/parser/beta/LinkInfo.java | 17 ++++++++-- .../commonmark/parser/beta/LinkProcessor.java | 32 +++++++++++++++++++ .../commonmark/parser/beta/LinkResult.java | 31 ++++++++++++++++++ 5 files changed, 85 insertions(+), 5 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java index 1f428d22c..74162d448 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -25,7 +25,7 @@ public interface InlineParserContext { List getCustomDelimiterProcessors(); /** - * TODO + * @return custom link processors that have been configured with {@link Parser.Builder#linkProcessor}. */ List getCustomLinkProcessors(); diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index c65680a40..571fbf8c4 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -248,7 +248,13 @@ public Builder customDelimiterProcessor(DelimiterProcessor delimiterProcessor) { } /** - * TODO + * Add a custom link/image processor for inline parsing. + *

    + * Multiple link processors can be added, and will be tried in order in which they were added. If no link + * processor applies, the normal behavior applies. That means these can override built-in link parsing. + * + * @param linkProcessor a link processor implementation + * @return {@code this} */ public Builder linkProcessor(LinkProcessor linkProcessor) { Objects.requireNonNull(linkProcessor, "linkProcessor must not be null"); diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java index 47dd8a9ea..969afb4bd 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java @@ -21,16 +21,27 @@ *

      * [text]
      * 
    + * Images use the same syntax as links but with a {@code !} in front, e.g. {@code ![text](destination)}. */ public interface LinkInfo { enum OpenerType { - // An image (a `!` before the `[`) + /** + * An image (a {@code !} before the {@code [}) + */ IMAGE, - // A link + /** + * A link + */ LINK } - // TODO: We could also expose the opener Text (`[` or `![`) + /** + * The type of opener of this link/image: + *
      + *
    • {@link OpenerType#LINK} for links like {@code [text...}
    • + *
    • {@link OpenerType#IMAGE} for images like {@code ![text...}
    • + *
    + */ OpenerType openerType(); /** diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java index 6d5a1cfaf..48193081f 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java @@ -2,7 +2,39 @@ import org.commonmark.parser.InlineParserContext; +/** + * An interface to decide how links/images are handled. + *

    + * Implementations need to be registered with a parser via {@link org.commonmark.parser.Parser.Builder#linkProcessor}. + * Then, when inline parsing is run, each parsed link/image is passed to the processor. This includes links like these: + *

    + *

    
    + * [text](destination)
    + * [text]
    + * [text][]
    + * [text][label]
    + * 
    + * And images: + *
    
    + * ![text](destination)
    + * ![text]
    + * ![text][]
    + * ![text][label]
    + * 
    + * See {@link LinkInfo} for accessing various parts of the parsed link/image. + *

    + * The processor can then inspect the link/image and decide what to do with it by returning the appropriate + * {@link LinkResult}. If it returns {@link LinkResult#none()}, the next registered processor is tried. If none of them + * apply, the link is handled as it normally would. + */ public interface LinkProcessor { + /** + * @param linkInfo information about the parsed link/image + * @param scanner the scanner at the current position after the parsed link/image + * @param context context for inline parsing + * @return what to do with the link/image, e.g. do nothing (try the next processor), wrap the text in a node, or + * replace the link/image with a node + */ LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContext context); } diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java index 69a3bebc5..d7edcc3d1 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java @@ -3,18 +3,49 @@ import org.commonmark.internal.inline.LinkResultImpl; import org.commonmark.node.Node; +/** + * What to do with a link/image processed by {@link LinkProcessor}. + */ public interface LinkResult { + /** + * Link not handled by processor. + */ static LinkResult none() { return null; } + /** + * Wrap the link text in a node. This is the normal behavior for links, e.g. for this: + *

    
    +     * [my *text*](destination)
    +     * 
    + * The text is {@code my *text*}, a text node and emphasis. The text is wrapped in a + * {@link org.commonmark.node.Link} node, which means the text is added as child nodes to it. + * + * @param node the node to which the link text nodes will be added as child nodes + * @param position the position to continue parsing from + */ static LinkResult wrapTextIn(Node node, Position position) { return new LinkResultImpl(LinkResultImpl.Type.WRAP, node, position); } + /** + * Replace the link with a node. E.g. for this: + *
    
    +     * [^foo]
    +     * 
    + * The processor could decide to create a {@code FootnoteReference} node instead which replaces the link. + * + * @param node the node to replace the link with + * @param position the position to continue parsing from + */ static LinkResult replaceWith(Node node, Position position) { return new LinkResultImpl(LinkResultImpl.Type.REPLACE, node, position); } + /** + * Instead of processing the full node (e.g. {@code ![image]}, only start from the bracket (e.g. {@code [image]}). + * This is useful for excluding the {@code !} character denoting an image. It will just be left as text instead. + */ LinkResult startFromBracket(); } From c68809dacbf739087ec97d39db98ca0cc0fbad0a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 6 Jul 2024 23:15:54 +1000 Subject: [PATCH 135/345] Documentation tweaks --- .../java/org/commonmark/node/DefinitionMap.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java b/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java index 9cc94507f..59cb88274 100644 --- a/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java +++ b/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java @@ -5,9 +5,10 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; /** - * A map that can be used to store and lookup reference definitions by a label. The labels are case-insensitive and + * A map that can be used to store and look up reference definitions by a label. The labels are case-insensitive and * normalized, the same way as for {@link LinkReferenceDefinition} nodes. * * @param the type of value @@ -28,13 +29,16 @@ public Class getType() { public void addAll(DefinitionMap that) { for (var entry : that.definitions.entrySet()) { + // Note that keys are already normalized, so we can add them directly definitions.putIfAbsent(entry.getKey(), entry.getValue()); } } /** - * Store a new definition unless one is already in the map. If there is no definition for that label, return null. + * Store a new definition unless one is already in the map. If there is no definition for that label yet, return null. * Otherwise, return the existing definition. + *

    + * The label is normalized by the definition map before storing. */ public D putIfAbsent(String label, D definition) { String normalizedLabel = Escaping.normalizeLabelContent(label); @@ -44,7 +48,7 @@ public D putIfAbsent(String label, D definition) { } /** - * Lookup a definition by normalized label. + * Look up a definition by label. The label is normalized by the definition map before lookup. * * @return the value or null */ @@ -53,6 +57,10 @@ public D get(String label) { return definitions.get(normalizedLabel); } + public Set keySet() { + return definitions.keySet(); + } + public Collection values() { return definitions.values(); } From ee7b710649717a93ab02dd98b0db147ed44eb717 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 7 Jul 2024 22:41:59 +1000 Subject: [PATCH 136/345] Fix Javadoc --- .../main/java/org/commonmark/parser/beta/LinkProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java index 48193081f..3e448fd91 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java @@ -13,14 +13,14 @@ * [text] * [text][] * [text][label] - * + * * And images: *

    
      * ![text](destination)
      * ![text]
      * ![text][]
      * ![text][label]
    - * 
    + * * See {@link LinkInfo} for accessing various parts of the parsed link/image. *

    * The processor can then inspect the link/image and decide what to do with it by returning the appropriate From 103f002b7a34a9a06ae854d0247f25ab4e9b1f5b Mon Sep 17 00:00:00 2001 From: Yuri Maltsev Date: Mon, 8 Jul 2024 10:52:49 +0200 Subject: [PATCH 137/345] render ThematicBreak literal in markdown --- .../renderer/markdown/CoreMarkdownNodeRenderer.java | 8 ++++++-- .../renderer/markdown/MarkdownRendererTest.java | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 8a9e57251..7042ad594 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -87,8 +87,12 @@ public void visit(Document document) { @Override public void visit(ThematicBreak thematicBreak) { - // Let's use ___ as it doesn't introduce ambiguity with * or - list item markers - writer.raw("___"); + String literal = thematicBreak.getLiteral(); + if (literal == null) { + // Let's use ___ as it doesn't introduce ambiguity with * or - list item markers + literal = "___"; + } + writer.raw(literal); writer.block(); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 91af1bfe8..9a849c58d 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -19,6 +19,14 @@ public void testThematicBreaks() { // List item with hr -> hr needs to not use the same as the marker assertRoundTrip("* ___\n"); assertRoundTrip("- ___\n"); + + // Preserve the literal + assertRoundTrip("----\n"); + assertRoundTrip("*****\n"); + + // Apply fallback for null literal + ThematicBreak node = new ThematicBreak(); + assertEquals("___", render(node)); } @Test From 6bfb5f4b56a8a0329a325dde2d217ae3942b914d Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 27 Aug 2024 15:55:24 +0200 Subject: [PATCH 138/345] Pin jitpack maven version compatible with the compiler plugin The default maven version on jitpack (3.6.1) fails to build commonmark-java with [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile (default-compile) on project commonmark-test-util: The plugin org.apache.maven.plugins:maven-compiler-plugin:3.13.0 requires Maven version 3.6.3 --- jitpack.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 jitpack.yml diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 000000000..7889e98bf --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,2 @@ +before_install: + - sdk install maven 3.6.3 From 0e979ef8f2fcd657214bf37fdd99aea8073068cf Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 28 Aug 2024 10:05:02 +0200 Subject: [PATCH 139/345] Use maven wrapper instead of configuring jitpack script generated with 'mvn wrapper:wrapper -Dmaven=3.6.3' --- .mvn/wrapper/maven-wrapper.properties | 19 ++ jitpack.yml | 2 - mvnw | 259 ++++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 .mvn/wrapper/maven-wrapper.properties delete mode 100644 jitpack.yml create mode 100755 mvnw diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..4d245050f --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip diff --git a/jitpack.yml b/jitpack.yml deleted file mode 100644 index 7889e98bf..000000000 --- a/jitpack.yml +++ /dev/null @@ -1,2 +0,0 @@ -before_install: - - sdk install maven 3.6.3 diff --git a/mvnw b/mvnw new file mode 100755 index 000000000..19529ddf8 --- /dev/null +++ b/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" From 72d6fa78a0093826d35cdcbdbd2c247a23f8b1ad Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 13 Jul 2024 21:21:42 +1000 Subject: [PATCH 140/345] Add support for inline footnotes See e.g. https://pandoc.org/MANUAL.html#extension-inline_notes --- .../ext/footnotes/FootnotesExtension.java | 46 ++++++++++++++++--- .../ext/footnotes/InlineFootnote.java | 6 +++ .../internal/FootnoteLinkProcessor.java | 12 ++++- .../internal/InlineFootnoteMarker.java | 6 +++ .../internal/InlineFootnoteMarkerParser.java | 40 ++++++++++++++++ .../internal/InlineFootnoteMarkerRemover.java | 29 ++++++++++++ .../ext/footnotes/FootnotesTest.java | 44 ++++++++++++++++++ .../commonmark/internal/InlineParserImpl.java | 13 ++++-- .../main/java/org/commonmark/node/Node.java | 7 ++- .../org/commonmark/parser/beta/LinkInfo.java | 10 +++- 10 files changed, 201 insertions(+), 12 deletions(-) create mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/InlineFootnote.java create mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarker.java create mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerParser.java create mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerRemover.java diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java index 9ac2c506e..48acc65aa 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java @@ -1,11 +1,9 @@ package org.commonmark.ext.footnotes; import org.commonmark.Extension; -import org.commonmark.ext.footnotes.internal.FootnoteBlockParser; -import org.commonmark.ext.footnotes.internal.FootnoteLinkProcessor; -import org.commonmark.ext.footnotes.internal.FootnoteHtmlNodeRenderer; -import org.commonmark.ext.footnotes.internal.FootnoteMarkdownNodeRenderer; +import org.commonmark.ext.footnotes.internal.*; import org.commonmark.parser.Parser; +import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; @@ -30,6 +28,8 @@ * the source. The footnotes will be numbered starting from 1, then 2, etc, depending on the order in which they appear * in the text (and not dependent on the label). The footnote reference is a link to the footnote, and from the footnote * there is a link back to the reference (or multiple). + *

    + * There is also optional support for inline footnotes, use {@link #builder()} and then set {@link Builder#inlineFootnotes}. * * @see GitHub docs for footnotes */ @@ -37,11 +37,21 @@ public class FootnotesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, MarkdownRenderer.MarkdownRendererExtension { - private FootnotesExtension() { + private final boolean inlineFootnotes; + + private FootnotesExtension(boolean inlineFootnotes) { + this.inlineFootnotes = inlineFootnotes; } + /** + * The extension with the default configuration (no support for inline footnotes). + */ public static Extension create() { - return new FootnotesExtension(); + return builder().build(); + } + + public static Builder builder() { + return new Builder(); } @Override @@ -49,6 +59,10 @@ public void extend(Parser.Builder parserBuilder) { parserBuilder .customBlockParserFactory(new FootnoteBlockParser.Factory()) .linkProcessor(new FootnoteLinkProcessor()); + if (inlineFootnotes) { + parserBuilder.customInlineContentParserFactory(new InlineFootnoteMarkerParser.Factory()) + .postProcessor(new InlineFootnoteMarkerRemover()); + } } @Override @@ -70,4 +84,24 @@ public Set getSpecialCharacters() { } }); } + + public static class Builder { + + private boolean inlineFootnotes = false; + + /** + * Enable support for inline footnotes without definitions, e.g.: + *

    +         * Some text^[this is an inline footnote]
    +         * 
    + */ + public Builder inlineFootnotes(boolean inlineFootnotes) { + this.inlineFootnotes = inlineFootnotes; + return this; + } + + public FootnotesExtension build() { + return new FootnotesExtension(inlineFootnotes); + } + } } diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/InlineFootnote.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/InlineFootnote.java new file mode 100644 index 000000000..665d01936 --- /dev/null +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/InlineFootnote.java @@ -0,0 +1,6 @@ +package org.commonmark.ext.footnotes; + +import org.commonmark.node.CustomNode; + +public class InlineFootnote extends CustomNode { +} diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java index 077ca5b70..ca8e2d557 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java @@ -2,7 +2,9 @@ import org.commonmark.ext.footnotes.FootnoteDefinition; import org.commonmark.ext.footnotes.FootnoteReference; +import org.commonmark.ext.footnotes.InlineFootnote; import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.node.Text; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.beta.LinkInfo; import org.commonmark.parser.beta.LinkProcessor; @@ -10,11 +12,19 @@ import org.commonmark.parser.beta.Scanner; /** - * For turning e.g. [^foo] into a {@link FootnoteReference}. + * For turning e.g. [^foo] into a {@link FootnoteReference}, + * and ^[foo] into an {@link InlineFootnote}. */ public class FootnoteLinkProcessor implements LinkProcessor { @Override public LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContext context) { + + var beforeBracket = linkInfo.openingBracket().getPrevious(); + if (beforeBracket instanceof InlineFootnoteMarker) { + beforeBracket.unlink(); + return LinkResult.wrapTextIn(new InlineFootnote(), linkInfo.afterTextBracket()); + } + if (linkInfo.destination() != null) { // If it's an inline link, it can't be a footnote reference return LinkResult.none(); diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarker.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarker.java new file mode 100644 index 000000000..7d1ebf802 --- /dev/null +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarker.java @@ -0,0 +1,6 @@ +package org.commonmark.ext.footnotes.internal; + +import org.commonmark.node.CustomNode; + +public class InlineFootnoteMarker extends CustomNode { +} diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerParser.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerParser.java new file mode 100644 index 000000000..7de1f74bd --- /dev/null +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerParser.java @@ -0,0 +1,40 @@ +package org.commonmark.ext.footnotes.internal; + +import org.commonmark.parser.beta.InlineContentParser; +import org.commonmark.parser.beta.InlineContentParserFactory; +import org.commonmark.parser.beta.InlineParserState; +import org.commonmark.parser.beta.ParsedInline; + +import java.util.Set; + +/** + * Parses any potential inline footnote markers (any `^` before `[`). Later we'll either use it in + * {@link FootnoteLinkProcessor}, or remove any unused ones in {@link InlineFootnoteMarkerRemover}. + */ +public class InlineFootnoteMarkerParser implements InlineContentParser { + @Override + public ParsedInline tryParse(InlineParserState inlineParserState) { + var scanner = inlineParserState.scanner(); + // Skip ^ + scanner.next(); + + if (scanner.peek() == '[') { + return ParsedInline.of(new InlineFootnoteMarker(), scanner.position()); + } else { + return ParsedInline.none(); + } + } + + public static class Factory implements InlineContentParserFactory { + + @Override + public Set getTriggerCharacters() { + return Set.of('^'); + } + + @Override + public InlineContentParser create() { + return new InlineFootnoteMarkerParser(); + } + } +} diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerRemover.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerRemover.java new file mode 100644 index 000000000..67fbd7111 --- /dev/null +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerRemover.java @@ -0,0 +1,29 @@ +package org.commonmark.ext.footnotes.internal; + +import org.commonmark.node.AbstractVisitor; +import org.commonmark.node.CustomNode; +import org.commonmark.node.Node; +import org.commonmark.node.Text; +import org.commonmark.parser.PostProcessor; + +/** + * Remove any markers that have been parsed by {@link InlineFootnoteMarkerParser} but haven't been used in + * {@link FootnoteLinkProcessor}. + */ +public class InlineFootnoteMarkerRemover extends AbstractVisitor implements PostProcessor { + @Override + public Node process(Node node) { + node.accept(this); + return node; + } + + @Override + public void visit(CustomNode customNode) { + if (customNode instanceof InlineFootnoteMarker) { + var text = new Text("^"); + text.setSourceSpans(customNode.getSourceSpans()); + customNode.insertAfter(text); + customNode.unlink(); + } + } +} diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index fd2dcd43d..25e645472 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -1,6 +1,7 @@ package org.commonmark.ext.footnotes; import org.commonmark.Extension; +import org.commonmark.ext.footnotes.internal.InlineFootnoteMarker; import org.commonmark.node.*; import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; @@ -235,6 +236,49 @@ public void testReferenceLinkWithoutDefinition() { assertText("[foo]", paragraph.getLastChild()); } + @Test + public void testInlineFootnote() { + var extension = FootnotesExtension.builder().inlineFootnotes(true).build(); + var parser = Parser.builder().extensions(Set.of(extension)).build(); + + { + var doc = parser.parse("Test ^[inline footnote]"); + assertText("Test ", doc.getFirstChild().getFirstChild()); + var fn = find(doc, InlineFootnote.class); + assertText("inline footnote", fn.getFirstChild()); + assertNull(tryFind(doc, InlineFootnoteMarker.class)); + } + + { + var doc = parser.parse("Test \\^[not inline footnote]"); + assertNull(tryFind(doc, InlineFootnote.class)); + } + + { + var doc = parser.parse("Test ^[not inline footnote"); + assertNull(tryFind(doc, InlineFootnote.class)); + assertNull(tryFind(doc, InlineFootnoteMarker.class)); + var t = doc.getFirstChild().getFirstChild(); + // This is not ideal; text nodes aren't merged after post-processing + assertText("Test ", t); + assertText("^", t.getNext()); + assertText("[not inline footnote", t.getNext().getNext()); + } + + { + // This is a tricky one because the code span in the link text + // includes the `]` (and doesn't need to be escaped). Therefore + // inline footnote parsing has to do full link text parsing/inline parsing. + // https://spec.commonmark.org/0.31.2/#link-text + + var doc = parser.parse("^[test `bla]`]"); + var fn = find(doc, InlineFootnote.class); + assertText("test ", fn.getFirstChild()); + var code = fn.getFirstChild().getNext(); + assertEquals("bla]", ((Code) code).getLiteral()); + } + } + @Test public void testSourcePositions() { var parser = Parser.builder().extensions(EXTENSIONS).includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES).build(); diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index bf6ac7b62..d18365b6d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -372,7 +372,7 @@ private LinkInfo parseLinkInfo(Bracket opener, Position beforeClose) { // Maybe an inline link/image var destinationTitle = parseInlineDestinationTitle(scanner); if (destinationTitle != null) { - return new LinkInfoImpl(openerType, text, null, destinationTitle.destination, destinationTitle.title, afterClose); + return new LinkInfoImpl(openerType, opener.bracketNode, text, null, destinationTitle.destination, destinationTitle.title, afterClose); } // Not an inline link/image, rewind back to after `]`. scanner.setPosition(afterClose); @@ -394,7 +394,7 @@ private LinkInfo parseLinkInfo(Bracket opener, Position beforeClose) { return null; } - return new LinkInfoImpl(openerType, text, label, null, null, afterClose); + return new LinkInfoImpl(openerType, opener.bracketNode, text, label, null, null, afterClose); } private Node wrapBracket(Bracket opener, Node wrapperNode, boolean startFromBracket) { @@ -879,15 +879,17 @@ public DestinationTitle(String destination, String title) { private static class LinkInfoImpl implements LinkInfo { private final OpenerType openerType; + private final Text openingBracket; private final String text; private final String label; private final String destination; private final String title; private final Position afterTextBracket; - private LinkInfoImpl(OpenerType openerType, String text, String label, + private LinkInfoImpl(OpenerType openerType, Text openingBracket, String text, String label, String destination, String title, Position afterTextBracket) { this.openerType = openerType; + this.openingBracket = openingBracket; this.text = text; this.label = label; this.destination = destination; @@ -900,6 +902,11 @@ public OpenerType openerType() { return openerType; } + @Override + public Text openingBracket() { + return openingBracket; + } + @Override public String text() { return text; diff --git a/commonmark/src/main/java/org/commonmark/node/Node.java b/commonmark/src/main/java/org/commonmark/node/Node.java index 4f806d550..d95a72c60 100644 --- a/commonmark/src/main/java/org/commonmark/node/Node.java +++ b/commonmark/src/main/java/org/commonmark/node/Node.java @@ -86,6 +86,9 @@ public void unlink() { this.prev = null; } + /** + * Inserts the {@code sibling} node after {@code this} node. + */ public void insertAfter(Node sibling) { sibling.unlink(); sibling.next = this.next; @@ -100,6 +103,9 @@ public void insertAfter(Node sibling) { } } + /** + * Inserts the {@code sibling} node before {@code this} node. + */ public void insertBefore(Node sibling) { sibling.unlink(); sibling.prev = this.prev; @@ -114,7 +120,6 @@ public void insertBefore(Node sibling) { } } - /** * @return the source spans of this node if included by the parser, an empty list otherwise * @since 0.16.0 diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java index 969afb4bd..cb14e4fad 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java @@ -1,5 +1,8 @@ package org.commonmark.parser.beta; +import org.commonmark.node.Node; +import org.commonmark.node.Text; + /** * A parsed link or image. There are different types of links. *

    @@ -44,6 +47,11 @@ enum OpenerType { */ OpenerType openerType(); + /** + * The text node of the opening bracket {@code [}. + */ + Text openingBracket(); + /** * The text between the first brackets, e.g. `foo` in `[foo][bar]`. */ @@ -65,7 +73,7 @@ enum OpenerType { String title(); /** - * The position after the text bracket, e.g.: + * The position after the closing text bracket, e.g.: *

          * [foo][bar]
          *      ^
    
    From be9a27fa431372ad94b73097c130c0257a0c219c Mon Sep 17 00:00:00 2001
    From: Robin Stocker 
    Date: Sat, 13 Jul 2024 23:34:45 +1000
    Subject: [PATCH 141/345] Generalize link markers and use for inline footnotes
    
    The resulting implementation for the footnotes extension is much nicer.
    It also cleans up LinkInfo and makes images less of a special case.
    
    Additionally, this allow inline parsing of markers that are not
    part of links - could have done this without this change but noticed
    it here and decided to fix it.
    ---
     .../ext/footnotes/FootnotesExtension.java     |  3 +-
     .../internal/FootnoteLinkProcessor.java       | 10 +--
     .../internal/InlineFootnoteMarker.java        |  6 --
     .../internal/InlineFootnoteMarkerParser.java  | 40 ---------
     .../internal/InlineFootnoteMarkerRemover.java | 29 -------
     .../ext/footnotes/FootnotesTest.java          | 16 ++--
     .../java/org/commonmark/internal/Bracket.java | 28 +++---
     .../commonmark/internal/DocumentParser.java   |  6 +-
     .../internal/InlineParserContextImpl.java     |  9 ++
     .../commonmark/internal/InlineParserImpl.java | 85 ++++++++++++-------
     .../internal/inline/CoreLinkProcessor.java    | 16 ++--
     .../internal/inline/LinkResultImpl.java       | 10 +--
     .../parser/InlineParserContext.java           |  6 ++
     .../java/org/commonmark/parser/Parser.java    | 31 +++++--
     .../org/commonmark/parser/beta/LinkInfo.java  | 24 ++----
     .../commonmark/parser/beta/LinkResult.java    |  5 +-
     .../parser/InlineContentParserTest.java       | 40 +++++++++
     .../parser/beta/LinkProcessorTest.java        | 25 ++++++
     .../test/InlineParserContextTest.java         |  6 ++
     19 files changed, 212 insertions(+), 183 deletions(-)
     delete mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarker.java
     delete mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerParser.java
     delete mode 100644 commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerRemover.java
     create mode 100644 commonmark/src/test/java/org/commonmark/parser/beta/LinkProcessorTest.java
    
    diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java
    index 48acc65aa..e2f90c239 100644
    --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java
    +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java
    @@ -60,8 +60,7 @@ public void extend(Parser.Builder parserBuilder) {
                     .customBlockParserFactory(new FootnoteBlockParser.Factory())
                     .linkProcessor(new FootnoteLinkProcessor());
             if (inlineFootnotes) {
    -            parserBuilder.customInlineContentParserFactory(new InlineFootnoteMarkerParser.Factory())
    -                    .postProcessor(new InlineFootnoteMarkerRemover());
    +            parserBuilder.linkMarker('^');
             }
         }
     
    diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java
    index ca8e2d557..b2b420bae 100644
    --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java
    +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java
    @@ -19,10 +19,10 @@ public class FootnoteLinkProcessor implements LinkProcessor {
         @Override
         public LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContext context) {
     
    -        var beforeBracket = linkInfo.openingBracket().getPrevious();
    -        if (beforeBracket instanceof InlineFootnoteMarker) {
    -            beforeBracket.unlink();
    -            return LinkResult.wrapTextIn(new InlineFootnote(), linkInfo.afterTextBracket());
    +        if (linkInfo.marker() != null && linkInfo.marker().getLiteral().equals("^")) {
    +            // An inline footnote like ^[footnote text]. Note that we only get the marker here if the option is enabled
    +            // on the extension.
    +            return LinkResult.wrapTextIn(new InlineFootnote(), linkInfo.afterTextBracket()).includeMarker();
             }
     
             if (linkInfo.destination() != null) {
    @@ -53,6 +53,6 @@ public LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContex
             // For footnotes, we only ever consume the text part of the link, not the label part (if any)
             var position = linkInfo.afterTextBracket();
             // If the marker is `![`, we don't want to include the `!`, so start from bracket
    -        return LinkResult.replaceWith(new FootnoteReference(label), position).startFromBracket();
    +        return LinkResult.replaceWith(new FootnoteReference(label), position);
         }
     }
    diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarker.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarker.java
    deleted file mode 100644
    index 7d1ebf802..000000000
    --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarker.java
    +++ /dev/null
    @@ -1,6 +0,0 @@
    -package org.commonmark.ext.footnotes.internal;
    -
    -import org.commonmark.node.CustomNode;
    -
    -public class InlineFootnoteMarker extends CustomNode {
    -}
    diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerParser.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerParser.java
    deleted file mode 100644
    index 7de1f74bd..000000000
    --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerParser.java
    +++ /dev/null
    @@ -1,40 +0,0 @@
    -package org.commonmark.ext.footnotes.internal;
    -
    -import org.commonmark.parser.beta.InlineContentParser;
    -import org.commonmark.parser.beta.InlineContentParserFactory;
    -import org.commonmark.parser.beta.InlineParserState;
    -import org.commonmark.parser.beta.ParsedInline;
    -
    -import java.util.Set;
    -
    -/**
    - * Parses any potential inline footnote markers (any `^` before `[`). Later we'll either use it in
    - * {@link FootnoteLinkProcessor}, or remove any unused ones in {@link InlineFootnoteMarkerRemover}.
    - */
    -public class InlineFootnoteMarkerParser implements InlineContentParser {
    -    @Override
    -    public ParsedInline tryParse(InlineParserState inlineParserState) {
    -        var scanner = inlineParserState.scanner();
    -        // Skip ^
    -        scanner.next();
    -
    -        if (scanner.peek() == '[') {
    -            return ParsedInline.of(new InlineFootnoteMarker(), scanner.position());
    -        } else {
    -            return ParsedInline.none();
    -        }
    -    }
    -
    -    public static class Factory implements InlineContentParserFactory {
    -
    -        @Override
    -        public Set getTriggerCharacters() {
    -            return Set.of('^');
    -        }
    -
    -        @Override
    -        public InlineContentParser create() {
    -            return new InlineFootnoteMarkerParser();
    -        }
    -    }
    -}
    diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerRemover.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerRemover.java
    deleted file mode 100644
    index 67fbd7111..000000000
    --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/InlineFootnoteMarkerRemover.java
    +++ /dev/null
    @@ -1,29 +0,0 @@
    -package org.commonmark.ext.footnotes.internal;
    -
    -import org.commonmark.node.AbstractVisitor;
    -import org.commonmark.node.CustomNode;
    -import org.commonmark.node.Node;
    -import org.commonmark.node.Text;
    -import org.commonmark.parser.PostProcessor;
    -
    -/**
    - * Remove any markers that have been parsed by {@link InlineFootnoteMarkerParser} but haven't been used in
    - * {@link FootnoteLinkProcessor}.
    - */
    -public class InlineFootnoteMarkerRemover extends AbstractVisitor implements PostProcessor {
    -    @Override
    -    public Node process(Node node) {
    -        node.accept(this);
    -        return node;
    -    }
    -
    -    @Override
    -    public void visit(CustomNode customNode) {
    -        if (customNode instanceof InlineFootnoteMarker) {
    -            var text = new Text("^");
    -            text.setSourceSpans(customNode.getSourceSpans());
    -            customNode.insertAfter(text);
    -            customNode.unlink();
    -        }
    -    }
    -}
    diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java
    index 25e645472..9090623cb 100644
    --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java
    +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java
    @@ -1,7 +1,6 @@
     package org.commonmark.ext.footnotes;
     
     import org.commonmark.Extension;
    -import org.commonmark.ext.footnotes.internal.InlineFootnoteMarker;
     import org.commonmark.node.*;
     import org.commonmark.parser.IncludeSourceSpans;
     import org.commonmark.parser.Parser;
    @@ -246,7 +245,6 @@ public void testInlineFootnote() {
                 assertText("Test ", doc.getFirstChild().getFirstChild());
                 var fn = find(doc, InlineFootnote.class);
                 assertText("inline footnote", fn.getFirstChild());
    -            assertNull(tryFind(doc, InlineFootnoteMarker.class));
             }
     
             {
    @@ -257,12 +255,8 @@ public void testInlineFootnote() {
             {
                 var doc = parser.parse("Test ^[not inline footnote");
                 assertNull(tryFind(doc, InlineFootnote.class));
    -            assertNull(tryFind(doc, InlineFootnoteMarker.class));
                 var t = doc.getFirstChild().getFirstChild();
    -            // This is not ideal; text nodes aren't merged after post-processing
    -            assertText("Test ", t);
    -            assertText("^", t.getNext());
    -            assertText("[not inline footnote", t.getNext().getNext());
    +            assertText("Test ^[not inline footnote", t);
             }
     
             {
    @@ -277,6 +271,14 @@ public void testInlineFootnote() {
                 var code = fn.getFirstChild().getNext();
                 assertEquals("bla]", ((Code) code).getLiteral());
             }
    +
    +        {
    +            var doc = parser.parse("^[with a [link](url)]");
    +            var fn = find(doc, InlineFootnote.class);
    +            assertText("with a ", fn.getFirstChild());
    +            var link = fn.getFirstChild().getNext();
    +            assertEquals("url", ((Link) link).getDestination());
    +        }
         }
     
         @Test
    diff --git a/commonmark/src/main/java/org/commonmark/internal/Bracket.java b/commonmark/src/main/java/org/commonmark/internal/Bracket.java
    index c2e14f4f1..c04b6ecda 100644
    --- a/commonmark/src/main/java/org/commonmark/internal/Bracket.java
    +++ b/commonmark/src/main/java/org/commonmark/internal/Bracket.java
    @@ -4,19 +4,19 @@
     import org.commonmark.parser.beta.Position;
     
     /**
    - * Opening bracket for links ([) or images (![).
    + * Opening bracket for links ({@code [}), images ({@code ![}), or links with other markers.
      */
     public class Bracket {
     
         /**
    -     * The node of {@code !} if present, null otherwise.
    +     * The node of a marker such as {@code !} if present, null otherwise.
          */
    -    public final Text bangNode;
    +    public final Text markerNode;
     
         /**
    -     * The position of {@code !} if present, null otherwise.
    +     * The position of the marker if present, null otherwise.
          */
    -    public final Position bangPosition;
    +    public final Position markerPosition;
     
         /**
          * The node of {@code [}.
    @@ -33,11 +33,6 @@ public class Bracket {
          */
         public final Position contentPosition;
     
    -    /**
    -     * Whether this is an image or link.
    -     */
    -    public final boolean image;
    -
         /**
          * Previous bracket.
          */
    @@ -59,20 +54,19 @@ public class Bracket {
         public boolean bracketAfter = false;
     
         static public Bracket link(Text bracketNode, Position bracketPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) {
    -        return new Bracket(null, null, bracketNode, bracketPosition, contentPosition, previous, previousDelimiter, false);
    +        return new Bracket(null, null, bracketNode, bracketPosition, contentPosition, previous, previousDelimiter);
         }
     
    -    static public Bracket image(Text bangNode, Position bangPosition, Text bracketNode, Position bracketPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) {
    -        return new Bracket(bangNode, bangPosition, bracketNode, bracketPosition, contentPosition, previous, previousDelimiter, true);
    +    static public Bracket withMarker(Text markerNode, Position markerPosition, Text bracketNode, Position bracketPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) {
    +        return new Bracket(markerNode, markerPosition, bracketNode, bracketPosition, contentPosition, previous, previousDelimiter);
         }
     
    -    private Bracket(Text bangNode, Position bangPosition, Text bracketNode, Position bracketPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter, boolean image) {
    -        this.bangNode = bangNode;
    -        this.bangPosition = bangPosition;
    +    private Bracket(Text markerNode, Position markerPosition, Text bracketNode, Position bracketPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) {
    +        this.markerNode = markerNode;
    +        this.markerPosition = markerPosition;
             this.bracketNode = bracketNode;
             this.bracketPosition = bracketPosition;
             this.contentPosition = contentPosition;
    -        this.image = image;
             this.previous = previous;
             this.previousDelimiter = previousDelimiter;
         }
    diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java
    index 0556626cf..107001742 100644
    --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java
    +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java
    @@ -74,6 +74,7 @@ public class DocumentParser implements ParserState {
         private final List inlineContentParserFactories;
         private final List delimiterProcessors;
         private final List linkProcessors;
    +    private final Set linkMarkers;
         private final IncludeSourceSpans includeSourceSpans;
         private final DocumentBlockParser documentBlockParser;
         private final Definitions definitions = new Definitions();
    @@ -83,12 +84,13 @@ public class DocumentParser implements ParserState {
     
         public DocumentParser(List blockParserFactories, InlineParserFactory inlineParserFactory,
                               List inlineContentParserFactories, List delimiterProcessors,
    -                          List linkProcessors, IncludeSourceSpans includeSourceSpans) {
    +                          List linkProcessors, Set linkMarkers, IncludeSourceSpans includeSourceSpans) {
             this.blockParserFactories = blockParserFactories;
             this.inlineParserFactory = inlineParserFactory;
             this.inlineContentParserFactories = inlineContentParserFactories;
             this.delimiterProcessors = delimiterProcessors;
             this.linkProcessors = linkProcessors;
    +        this.linkMarkers = linkMarkers;
             this.includeSourceSpans = includeSourceSpans;
     
             this.documentBlockParser = new DocumentBlockParser();
    @@ -466,7 +468,7 @@ private BlockStartImpl findBlockStart(BlockParser blockParser) {
          * Walk through a block & children recursively, parsing string content into inline content where appropriate.
          */
         private void processInlines() {
    -        var context = new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, linkProcessors, definitions);
    +        var context = new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, linkProcessors, linkMarkers, definitions);
             var inlineParser = inlineParserFactory.create(context);
     
             for (var blockParser : allBlockParsers) {
    diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java
    index 2f03b2052..233041f62 100644
    --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java
    +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java
    @@ -7,21 +7,25 @@
     import org.commonmark.parser.delimiter.DelimiterProcessor;
     
     import java.util.List;
    +import java.util.Set;
     
     public class InlineParserContextImpl implements InlineParserContext {
     
         private final List inlineContentParserFactories;
         private final List delimiterProcessors;
         private final List linkProcessors;
    +    private final Set linkMarkers;
         private final Definitions definitions;
     
         public InlineParserContextImpl(List inlineContentParserFactories,
                                        List delimiterProcessors,
                                        List linkProcessors,
    +                                   Set linkMarkers,
                                        Definitions definitions) {
             this.inlineContentParserFactories = inlineContentParserFactories;
             this.delimiterProcessors = delimiterProcessors;
             this.linkProcessors = linkProcessors;
    +        this.linkMarkers = linkMarkers;
             this.definitions = definitions;
         }
     
    @@ -40,6 +44,11 @@ public List getCustomLinkProcessors() {
             return linkProcessors;
         }
     
    +    @Override
    +    public Set getCustomLinkMarkers() {
    +        return linkMarkers;
    +    }
    +
         @Override
         public LinkReferenceDefinition getLinkReferenceDefinition(String label) {
             return definitions.getDefinition(LinkReferenceDefinition.class, label);
    diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java
    index d18365b6d..44f153e00 100644
    --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java
    +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java
    @@ -21,6 +21,7 @@ public class InlineParserImpl implements InlineParser, InlineParserState {
         private final Map delimiterProcessors;
         private final List linkProcessors;
         private final BitSet specialCharacters;
    +    private final BitSet linkMarkers;
     
         private Map> inlineParsers;
         private Scanner scanner;
    @@ -43,7 +44,8 @@ public InlineParserImpl(InlineParserContext context) {
             this.inlineContentParserFactories = calculateInlineContentParserFactories(context.getCustomInlineContentParserFactories());
             this.delimiterProcessors = calculateDelimiterProcessors(context.getCustomDelimiterProcessors());
             this.linkProcessors = calculateLinkProcessors(context.getCustomLinkProcessors());
    -        this.specialCharacters = calculateSpecialCharacters(this.delimiterProcessors.keySet(), this.inlineContentParserFactories);
    +        this.linkMarkers = calculateLinkMarkers(context.getCustomLinkMarkers());
    +        this.specialCharacters = calculateSpecialCharacters(linkMarkers, this.delimiterProcessors.keySet(), this.inlineContentParserFactories);
         }
     
         private List calculateInlineContentParserFactories(List customFactories) {
    @@ -104,9 +106,19 @@ private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterPr
             }
         }
     
    -    private static BitSet calculateSpecialCharacters(Set delimiterCharacters,
    +    private static BitSet calculateLinkMarkers(Set linkMarkers) {
    +        var bitSet = new BitSet();
    +        for (var c : linkMarkers) {
    +            bitSet.set(c);
    +        }
    +        bitSet.set('!');
    +        return bitSet;
    +    }
    +
    +    private static BitSet calculateSpecialCharacters(BitSet linkMarkers,
    +                                                     Set delimiterCharacters,
                                                          List inlineContentParserFactories) {
    -        BitSet bitSet = new BitSet();
    +        BitSet bitSet = (BitSet) linkMarkers.clone();
             for (Character c : delimiterCharacters) {
                 bitSet.set(c);
             }
    @@ -185,8 +197,6 @@ private List parseInline() {
             switch (c) {
                 case '[':
                     return List.of(parseOpenBracket());
    -            case '!':
    -                return parseBang();
                 case ']':
                     return List.of(parseCloseBracket());
                 case '\n':
    @@ -195,6 +205,16 @@ private List parseInline() {
                     return null;
             }
     
    +        if (linkMarkers.get(c)) {
    +            var markerPosition = scanner.position();
    +            var nodes = parseLinkMarker();
    +            if (nodes != null) {
    +                return nodes;
    +            }
    +            // Reset and try other things (e.g. inline parsers below)
    +            scanner.setPosition(markerPosition);
    +        }
    +
             // No inline parser, delimiter or other special handling.
             if (!specialCharacters.get(c)) {
                 return List.of(parseText());
    @@ -269,23 +289,23 @@ private Node parseOpenBracket() {
         }
     
         /**
    -     * If next character is [, and ! delimiter to delimiter stack and add a text node to block's children.
    -     * Otherwise just add a text node.
    +     * If next character is {@code [}, add a bracket to the stack.
    +     * Otherwise, return null.
          */
    -    private List parseBang() {
    -        var bangPosition = scanner.position();
    +    private List parseLinkMarker() {
    +        var markerPosition = scanner.position();
             scanner.next();
             var bracketPosition = scanner.position();
             if (scanner.next('[')) {
                 var contentPosition = scanner.position();
    -            var bangNode = text(scanner.getSource(bangPosition, bracketPosition));
    +            var bangNode = text(scanner.getSource(markerPosition, bracketPosition));
                 var bracketNode = text(scanner.getSource(bracketPosition, contentPosition));
     
                 // Add entry to stack for this opener
    -            addBracket(Bracket.image(bangNode, bangPosition, bracketNode, bracketPosition, contentPosition, lastBracket, lastDelimiter));
    +            addBracket(Bracket.withMarker(bangNode, markerPosition, bracketNode, bracketPosition, contentPosition, lastBracket, lastDelimiter));
                 return List.of(bangNode, bracketNode);
             } else {
    -            return List.of(text(scanner.getSource(bangPosition, scanner.position())));
    +            return null;
             }
         }
     
    @@ -340,15 +360,15 @@ private Node parseLinkOrImage(Bracket opener, Position beforeClose) {
                 var result = (LinkResultImpl) linkResult;
                 var node = result.getNode();
                 var position = result.getPosition();
    -            var startFromBracket = result.isStartFromBracket();
    +            var includeMarker = result.isIncludeMarker();
     
                 switch (result.getType()) {
                     case WRAP:
                         scanner.setPosition(position);
    -                    return wrapBracket(opener, node, startFromBracket);
    +                    return wrapBracket(opener, node, includeMarker);
                     case REPLACE:
                         scanner.setPosition(position);
    -                    return replaceBracket(opener, node, startFromBracket);
    +                    return replaceBracket(opener, node, includeMarker);
                 }
             }
     
    @@ -363,7 +383,6 @@ private LinkInfo parseLinkInfo(Bracket opener, Position beforeClose) {
             //   - Collapsed: `[foo][]`    (foo is both the text and label)
             //   - Shortcut:  `[foo]`      (foo is both the text and label)
     
    -        var openerType = opener.image ? LinkInfo.OpenerType.IMAGE : LinkInfo.OpenerType.LINK;
             String text = scanner.getSource(opener.contentPosition, beforeClose).getContent();
     
             // Starting position is after the closing `]`
    @@ -372,7 +391,7 @@ private LinkInfo parseLinkInfo(Bracket opener, Position beforeClose) {
             // Maybe an inline link/image
             var destinationTitle = parseInlineDestinationTitle(scanner);
             if (destinationTitle != null) {
    -            return new LinkInfoImpl(openerType, opener.bracketNode, text, null, destinationTitle.destination, destinationTitle.title, afterClose);
    +            return new LinkInfoImpl(opener.markerNode, opener.bracketNode, text, null, destinationTitle.destination, destinationTitle.title, afterClose);
             }
             // Not an inline link/image, rewind back to after `]`.
             scanner.setPosition(afterClose);
    @@ -388,16 +407,16 @@ private LinkInfo parseLinkInfo(Bracket opener, Position beforeClose) {
                 scanner.setPosition(afterClose);
             }
             var textIsReference = label == null || label.isEmpty();
    -        if (opener.bracketAfter && textIsReference) {
    +        if (opener.bracketAfter && textIsReference && opener.markerNode == null) {
                 // In case of shortcut or collapsed links, the text is used as the reference. But the reference is not allowed to
                 // contain an unescaped bracket, so if that's the case we don't need to continue. This is an optimization.
                 return null;
             }
     
    -        return new LinkInfoImpl(openerType, opener.bracketNode, text, label, null, null, afterClose);
    +        return new LinkInfoImpl(opener.markerNode, opener.bracketNode, text, label, null, null, afterClose);
         }
     
    -    private Node wrapBracket(Bracket opener, Node wrapperNode, boolean startFromBracket) {
    +    private Node wrapBracket(Bracket opener, Node wrapperNode, boolean includeMarker) {
             // Add all nodes between the opening bracket and now (closing bracket) as child nodes of the link
             Node n = opener.bracketNode.getNext();
             while (n != null) {
    @@ -407,7 +426,7 @@ private Node wrapBracket(Bracket opener, Node wrapperNode, boolean startFromBrac
             }
     
             if (includeSourceSpans) {
    -            var startPosition = opener.bangPosition == null || startFromBracket ? opener.bracketPosition : opener.bangPosition;
    +            var startPosition = includeMarker && opener.markerPosition != null ? opener.markerPosition : opener.bracketPosition;
                 wrapperNode.setSourceSpans(scanner.getSource(startPosition, scanner.position()).getSourceSpans());
             }
     
    @@ -415,17 +434,17 @@ private Node wrapBracket(Bracket opener, Node wrapperNode, boolean startFromBrac
             processDelimiters(opener.previousDelimiter);
             mergeChildTextNodes(wrapperNode);
             // We don't need the corresponding text node anymore, we turned it into a link/image node
    -        if (opener.bangNode != null && !startFromBracket) {
    -            opener.bangNode.unlink();
    +        if (includeMarker && opener.markerNode != null) {
    +            opener.markerNode.unlink();
             }
             opener.bracketNode.unlink();
             removeLastBracket();
     
             // Links within links are not allowed. We found this link, so there can be no other link around it.
    -        if (!opener.image) {
    +        if (opener.markerNode == null) {
                 Bracket bracket = lastBracket;
                 while (bracket != null) {
    -                if (!bracket.image) {
    +                if (bracket.markerNode == null) {
                         // Disallow link opener. It will still get matched, but will not result in a link.
                         bracket.allowed = false;
                     }
    @@ -436,21 +455,21 @@ private Node wrapBracket(Bracket opener, Node wrapperNode, boolean startFromBrac
             return wrapperNode;
         }
     
    -    private Node replaceBracket(Bracket opener, Node node, boolean startFromBracket) {
    +    private Node replaceBracket(Bracket opener, Node node, boolean includeMarker) {
             // Remove delimiters (but keep text nodes)
             while (lastDelimiter != null && lastDelimiter != opener.previousDelimiter) {
                 removeDelimiterKeepNode(lastDelimiter);
             }
     
             if (includeSourceSpans) {
    -            var startPosition = opener.bangPosition == null || startFromBracket ? opener.bracketPosition : opener.bangPosition;
    +            var startPosition = includeMarker && opener.markerPosition != null ? opener.markerPosition : opener.bracketPosition;
                 node.setSourceSpans(scanner.getSource(startPosition, scanner.position()).getSourceSpans());
             }
     
             removeLastBracket();
     
             // Remove nodes that we added since the opener, because we're replacing them
    -        Node n = opener.bangNode == null || startFromBracket ? opener.bracketNode : opener.bangNode;
    +        Node n = includeMarker && opener.markerNode != null ? opener.markerNode : opener.bracketNode;
             while (n != null) {
                 var next = n.getNext();
                 n.unlink();
    @@ -878,7 +897,7 @@ public DestinationTitle(String destination, String title) {
     
         private static class LinkInfoImpl implements LinkInfo {
     
    -        private final OpenerType openerType;
    +        private final Text marker;
             private final Text openingBracket;
             private final String text;
             private final String label;
    @@ -886,9 +905,9 @@ private static class LinkInfoImpl implements LinkInfo {
             private final String title;
             private final Position afterTextBracket;
     
    -        private LinkInfoImpl(OpenerType openerType, Text openingBracket, String text, String label,
    +        private LinkInfoImpl(Text marker, Text openingBracket, String text, String label,
                                  String destination, String title, Position afterTextBracket) {
    -            this.openerType = openerType;
    +            this.marker = marker;
                 this.openingBracket = openingBracket;
                 this.text = text;
                 this.label = label;
    @@ -898,8 +917,8 @@ private LinkInfoImpl(OpenerType openerType, Text openingBracket, String text, St
             }
     
             @Override
    -        public OpenerType openerType() {
    -            return openerType;
    +        public Text marker() {
    +            return marker;
             }
     
             @Override
    diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/CoreLinkProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/CoreLinkProcessor.java
    index 896b375da..528750aba 100644
    --- a/commonmark/src/main/java/org/commonmark/internal/inline/CoreLinkProcessor.java
    +++ b/commonmark/src/main/java/org/commonmark/internal/inline/CoreLinkProcessor.java
    @@ -3,7 +3,6 @@
     import org.commonmark.node.Image;
     import org.commonmark.node.Link;
     import org.commonmark.node.LinkReferenceDefinition;
    -import org.commonmark.node.Node;
     import org.commonmark.parser.InlineParserContext;
     import org.commonmark.parser.beta.LinkInfo;
     import org.commonmark.parser.beta.LinkProcessor;
    @@ -16,8 +15,7 @@ public class CoreLinkProcessor implements LinkProcessor {
         public LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContext context) {
             if (linkInfo.destination() != null) {
                 // Inline link
    -            var node = createNode(linkInfo, linkInfo.destination(), linkInfo.title());
    -            return LinkResult.wrapTextIn(node, scanner.position());
    +            return process(linkInfo, scanner, linkInfo.destination(), linkInfo.title());
             }
     
             var label = linkInfo.label();
    @@ -25,15 +23,15 @@ public LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContex
             var def = context.getDefinition(LinkReferenceDefinition.class, ref);
             if (def != null) {
                 // Reference link
    -            var node = createNode(linkInfo, def.getDestination(), def.getTitle());
    -            return LinkResult.wrapTextIn(node, scanner.position());
    +            return process(linkInfo, scanner, def.getDestination(), def.getTitle());
             }
             return LinkResult.none();
         }
     
    -    private static Node createNode(LinkInfo linkInfo, String destination, String title) {
    -        return linkInfo.openerType() == LinkInfo.OpenerType.IMAGE ?
    -                new Image(destination, title) :
    -                new Link(destination, title);
    +    private static LinkResult process(LinkInfo linkInfo, Scanner scanner, String destination, String title) {
    +        if (linkInfo.marker() != null && linkInfo.marker().getLiteral().equals("!")) {
    +            return LinkResult.wrapTextIn(new Image(destination, title), scanner.position()).includeMarker();
    +        }
    +        return LinkResult.wrapTextIn(new Link(destination, title), scanner.position());
         }
     }
    diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/LinkResultImpl.java b/commonmark/src/main/java/org/commonmark/internal/inline/LinkResultImpl.java
    index d51f2f4cf..c05b24451 100644
    --- a/commonmark/src/main/java/org/commonmark/internal/inline/LinkResultImpl.java
    +++ b/commonmark/src/main/java/org/commonmark/internal/inline/LinkResultImpl.java
    @@ -6,8 +6,8 @@
     
     public class LinkResultImpl implements LinkResult {
         @Override
    -    public LinkResult startFromBracket() {
    -        startFromBracket = true;
    +    public LinkResult includeMarker() {
    +        includeMarker = true;
             return this;
         }
     
    @@ -20,7 +20,7 @@ public enum Type {
         private final Node node;
         private final Position position;
     
    -    private boolean startFromBracket;
    +    private boolean includeMarker = false;
     
         public LinkResultImpl(Type type, Node node, Position position) {
             this.type = type;
    @@ -40,7 +40,7 @@ public Position getPosition() {
             return position;
         }
     
    -    public boolean isStartFromBracket() {
    -        return startFromBracket;
    +    public boolean isIncludeMarker() {
    +        return includeMarker;
         }
     }
    diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java
    index 74162d448..12007610b 100644
    --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java
    +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java
    @@ -6,6 +6,7 @@
     import org.commonmark.parser.delimiter.DelimiterProcessor;
     
     import java.util.List;
    +import java.util.Set;
     
     /**
      * Context for inline parsing.
    @@ -29,6 +30,11 @@ public interface InlineParserContext {
          */
         List getCustomLinkProcessors();
     
    +    /**
    +     * @return custom link markers that have been configured with {@link Parser.Builder#linkMarker}.
    +     */
    +    Set getCustomLinkMarkers();
    +
         /**
          * Look up a {@link LinkReferenceDefinition} for a given label.
          * 

    diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 571fbf8c4..150f3aaf9 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -6,17 +6,16 @@ import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; import org.commonmark.node.*; +import org.commonmark.parser.beta.LinkInfo; import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; +import org.commonmark.parser.beta.LinkResult; import org.commonmark.parser.block.BlockParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; import java.io.IOException; import java.io.Reader; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; /** @@ -34,6 +33,7 @@ public class Parser { private final List inlineContentParserFactories; private final List delimiterProcessors; private final List linkProcessors; + private final Set linkMarkers; private final InlineParserFactory inlineParserFactory; private final List postProcessors; private final IncludeSourceSpans includeSourceSpans; @@ -45,12 +45,13 @@ private Parser(Builder builder) { this.inlineContentParserFactories = builder.inlineContentParserFactories; this.delimiterProcessors = builder.delimiterProcessors; this.linkProcessors = builder.linkProcessors; + this.linkMarkers = builder.linkMarkers; this.includeSourceSpans = builder.includeSourceSpans; // Try to construct an inline parser. Invalid configuration might result in an exception, which we want to // detect as soon as possible. var context = new InlineParserContextImpl( - inlineContentParserFactories, delimiterProcessors, linkProcessors, new Definitions()); + inlineContentParserFactories, delimiterProcessors, linkProcessors, linkMarkers, new Definitions()); this.inlineParserFactory.create(context); } @@ -105,7 +106,7 @@ public Node parseReader(Reader input) throws IOException { private DocumentParser createDocumentParser() { return new DocumentParser(blockParserFactories, inlineParserFactory, inlineContentParserFactories, - delimiterProcessors, linkProcessors, includeSourceSpans); + delimiterProcessors, linkProcessors, linkMarkers, includeSourceSpans); } private Node postProcess(Node document) { @@ -124,6 +125,7 @@ public static class Builder { private final List delimiterProcessors = new ArrayList<>(); private final List linkProcessors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); + private final Set linkMarkers = new HashSet<>(); private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); private InlineParserFactory inlineParserFactory; private IncludeSourceSpans includeSourceSpans = IncludeSourceSpans.NONE; @@ -262,6 +264,23 @@ public Builder linkProcessor(LinkProcessor linkProcessor) { return this; } + /** + * Add a custom link marker for link processing. A link marker is a character like {@code !} which, if it + * appears before the {@code [} of a link, changes the meaning of the link. + *

    + * If a link marker followed by a valid link is parsed, the {@link org.commonmark.parser.beta.LinkInfo} + * that is passed to {@link LinkProcessor} will have its {@link LinkInfo#marker()} set. A link processor should + * check the {@link Text#getLiteral()} and then do any processing, and will probably want to use {@link LinkResult#includeMarker()}. + * + * @param linkMarker a link marker character + * @return {@code this} + */ + public Builder linkMarker(Character linkMarker) { + Objects.requireNonNull(linkMarker, "linkMarker must not be null"); + linkMarkers.add(linkMarker); + return this; + } + public Builder postProcessor(PostProcessor postProcessor) { Objects.requireNonNull(postProcessor, "postProcessor must not be null"); postProcessors.add(postProcessor); diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java index cb14e4fad..b2fda57e4 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java @@ -1,10 +1,9 @@ package org.commonmark.parser.beta; -import org.commonmark.node.Node; import org.commonmark.node.Text; /** - * A parsed link or image. There are different types of links. + * A parsed link/image. There are different types of links. *

    * Inline links: *

    @@ -24,28 +23,15 @@
      * 
      * [text]
      * 
    - * Images use the same syntax as links but with a {@code !} in front, e.g. {@code ![text](destination)}. + * Images use the same syntax as links but with a {@code !} {@link #marker()} front, e.g. {@code ![text](destination)}. */ public interface LinkInfo { - enum OpenerType { - /** - * An image (a {@code !} before the {@code [}) - */ - IMAGE, - /** - * A link - */ - LINK - } /** - * The type of opener of this link/image: - *
      - *
    • {@link OpenerType#LINK} for links like {@code [text...}
    • - *
    • {@link OpenerType#IMAGE} for images like {@code ![text...}
    • - *
    + * The marker if present, or null. A marker is e.g. {@code !} for an image, or a custom marker as specified in + * {@link org.commonmark.parser.Parser.Builder#linkMarker}. */ - OpenerType openerType(); + Text marker(); /** * The text node of the opening bracket {@code [}. diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java index d7edcc3d1..43bc82af8 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java @@ -44,8 +44,7 @@ static LinkResult replaceWith(Node node, Position position) { } /** - * Instead of processing the full node (e.g. {@code ![image]}, only start from the bracket (e.g. {@code [image]}). - * This is useful for excluding the {@code !} character denoting an image. It will just be left as text instead. + * If a {@link LinkInfo#marker()} is present, include it in processing (i.e. treat it the same way as the brackets). */ - LinkResult startFromBracket(); + LinkResult includeMarker(); } diff --git a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java index 28e9b5748..ed97b6f31 100644 --- a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java @@ -2,6 +2,8 @@ import org.commonmark.node.CustomNode; import org.commonmark.node.Heading; +import org.commonmark.node.Image; +import org.commonmark.node.Text; import org.commonmark.parser.beta.InlineContentParser; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.beta.InlineParserState; @@ -35,6 +37,19 @@ public void customInlineContentParser() { assertEquals(0, inline3.getIndex()); } + @Test + public void bangInlineContentParser() { + // See if using ! for a custom inline content parser works. + // ![] is used for images, but if it's not followed by a [, it should be possible to parse it differently. + var parser = Parser.builder().customInlineContentParserFactory(new BangInlineParser.Factory()).build(); + var doc = parser.parse("![image](url) !notimage"); + var image = Nodes.find(doc, Image.class); + assertEquals("url", image.getDestination()); + assertEquals(" ", ((Text) image.getNext()).getLiteral()); + assertEquals(BangInline.class, image.getNext().getNext().getClass()); + assertEquals("notimage", ((Text) image.getNext().getNext().getNext()).getLiteral()); + } + private static class DollarInline extends CustomNode { private final String literal; private final int index; @@ -84,4 +99,29 @@ public InlineContentParser create() { } } } + + private static class BangInline extends CustomNode { + } + + private static class BangInlineParser implements InlineContentParser { + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState) { + var scanner = inlineParserState.scanner(); + scanner.next(); + return ParsedInline.of(new BangInline(), scanner.position()); + } + + static class Factory implements InlineContentParserFactory { + @Override + public Set getTriggerCharacters() { + return Set.of('!'); + } + + @Override + public InlineContentParser create() { + return new BangInlineParser(); + } + } + } } diff --git a/commonmark/src/test/java/org/commonmark/parser/beta/LinkProcessorTest.java b/commonmark/src/test/java/org/commonmark/parser/beta/LinkProcessorTest.java new file mode 100644 index 000000000..6209ac9a0 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/parser/beta/LinkProcessorTest.java @@ -0,0 +1,25 @@ +package org.commonmark.parser.beta; + +import org.commonmark.node.Link; +import org.commonmark.node.Text; +import org.commonmark.parser.Parser; +import org.commonmark.test.Nodes; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class LinkProcessorTest { + @Test + public void testLinkMarkerShouldNotBeIncludedByDefault() { + // If a link marker is registered but is not processed, the built-in link processor shouldn't consume it. + // And I think by default, other processors shouldn't consume it either (by accident). + // So requiring processors to opt into including the marker is better than requiring them to opt out, + // because processors that look for a marker already need to write some code to deal with the marker anyway, + // and will have tests ensuring that the marker is part of the parsed node, not the text. + var parser = Parser.builder().linkMarker('^').build(); + var doc = parser.parse("^[test](url)"); + var link = Nodes.find(doc, Link.class); + assertEquals("url", link.getDestination()); + assertEquals("^", ((Text) link.getPrevious()).getLiteral()); + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java index 264eb35d6..2f2463ccb 100644 --- a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java +++ b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Set; import static org.junit.Assert.assertEquals; @@ -57,6 +58,11 @@ public List getCustomLinkProcessors() { return inlineParserContext.getCustomLinkProcessors(); } + @Override + public Set getCustomLinkMarkers() { + return inlineParserContext.getCustomLinkMarkers(); + } + @Override public LinkReferenceDefinition getLinkReferenceDefinition(String label) { return getDefinition(LinkReferenceDefinition.class, label); From 257e4a44469436cc259afd15831ce2e3edc4b31a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 7 Sep 2024 17:39:02 +1000 Subject: [PATCH 142/345] Inline footnotes rendering (first part) --- .../internal/FootnoteHtmlNodeRenderer.java | 91 ++++++++++++------- .../footnotes/FootnoteHtmlRendererTest.java | 25 +++++ 2 files changed, 85 insertions(+), 31 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java index dbae3ab90..2fe59b195 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java @@ -2,6 +2,7 @@ import org.commonmark.ext.footnotes.FootnoteDefinition; import org.commonmark.ext.footnotes.FootnoteReference; +import org.commonmark.ext.footnotes.InlineFootnote; import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.HtmlNodeRendererContext; @@ -60,7 +61,7 @@ public class FootnoteHtmlNodeRenderer implements NodeRenderer { /** * Definitions that were referenced, in order in which they should be rendered. */ - private final Map referencedDefinitions = new LinkedHashMap<>(); + private final Map referencedDefinitions = new LinkedHashMap<>(); /** * Information about references that should be rendered as footnotes. This doesn't contain all references, just the @@ -75,7 +76,7 @@ public FootnoteHtmlNodeRenderer(HtmlNodeRendererContext context) { @Override public Set> getNodeTypes() { - return Set.of(FootnoteReference.class, FootnoteDefinition.class); + return Set.of(FootnoteReference.class, InlineFootnote.class, FootnoteDefinition.class); } @Override @@ -92,8 +93,16 @@ public void render(Node node) { // This is called for all references, even ones inside definitions that we render at the end. var ref = (FootnoteReference) node; // Use containsKey because if value is null, we don't need to try registering again. - var info = references.containsKey(ref) ? references.get(ref) : registerReference(ref); - renderReference(ref, info); + var info = references.containsKey(ref) ? references.get(ref) : tryRegisterReference(ref); + if (info != null) { + renderReference(ref, info); + } else { + // A reference without a corresponding definition is rendered as plain text + html.text("[^" + ref.getLabel() + "]"); + } + } else if (node instanceof InlineFootnote) { + var info = registerReference(node, null); + renderReference(node, info); } } @@ -114,7 +123,7 @@ public void afterRoot(Node node) { html.line(); // Check whether there are any footnotes inside the definitions that we're about to render. For those, we might - // need to render more definitions. So do a breadth-first search to find all relevant definition. + // need to render more definitions. So do a breadth-first search to find all relevant definitions. var check = new LinkedList<>(referencedDefinitions.keySet()); while (!check.isEmpty()) { var def = check.removeFirst(); @@ -124,7 +133,7 @@ public void afterRoot(Node node) { if (!referencedDefinitions.containsKey(d)) { check.addLast(d); } - references.put(ref, registerReference(ref)); + references.put(ref, registerReference(d, d.getLabel())); } })); } @@ -140,52 +149,50 @@ public void afterRoot(Node node) { html.line(); } - private ReferenceInfo registerReference(FootnoteReference ref) { + private ReferenceInfo tryRegisterReference(FootnoteReference ref) { var def = definitionMap.get(ref.getLabel()); if (def == null) { return null; } + return registerReference(def, def.getLabel()); + } + private ReferenceInfo registerReference(Node node, String label) { // The first referenced definition gets number 1, second one 2, etc. - var referencedDef = referencedDefinitions.computeIfAbsent(def, k -> new ReferencedDefinition(referencedDefinitions.size() + 1)); + var referencedDef = referencedDefinitions.computeIfAbsent(node, k -> { + var num = referencedDefinitions.size() + 1; + var key = definitionKey(label, num); + return new ReferencedDefinition(num, key); + }); var definitionNumber = referencedDef.definitionNumber; // The reference number for that particular definition. E.g. if there's two references for the same definition, // the first one is 1, the second one 2, etc. This is needed to give each reference a unique ID so that each // reference can get its own backlink from the definition. var refNumber = referencedDef.references.size() + 1; - var id = referenceId(def.getLabel(), refNumber); + var definitionKey = referencedDef.definitionKey; + var id = referenceId(definitionKey, refNumber); referencedDef.references.add(id); - var definitionId = definitionId(def.getLabel()); - - return new ReferenceInfo(id, definitionId, definitionNumber); + return new ReferenceInfo(id, definitionId(definitionKey), definitionNumber); } - private void renderReference(FootnoteReference ref, ReferenceInfo referenceInfo) { - if (referenceInfo == null) { - // A reference without a corresponding definition is rendered as plain text - html.text("[^" + ref.getLabel() + "]"); - return; - } - - html.tag("sup", context.extendAttributes(ref, "sup", Map.of("class", "footnote-ref"))); + private void renderReference(Node node, ReferenceInfo referenceInfo) { + html.tag("sup", context.extendAttributes(node, "sup", Map.of("class", "footnote-ref"))); var href = "#" + referenceInfo.definitionId; var attrs = new LinkedHashMap(); attrs.put("href", href); attrs.put("id", referenceInfo.id); attrs.put("data-footnote-ref", null); - html.tag("a", context.extendAttributes(ref, "a", attrs)); + html.tag("a", context.extendAttributes(node, "a", attrs)); html.raw(String.valueOf(referenceInfo.definitionNumber)); html.tag("/a"); html.tag("/sup"); } - private void renderDefinition(FootnoteDefinition def, ReferencedDefinition referencedDefinition) { - //
      etc - var id = definitionId(def.getLabel()); + private void renderDefinition(Node def, ReferencedDefinition referencedDefinition) { var attrs = new LinkedHashMap(); - attrs.put("id", id); + attrs.put("id", definitionId(referencedDefinition.definitionKey)); html.tag("li", context.extendAttributes(def, "li", attrs)); html.line(); @@ -213,6 +220,13 @@ private void renderDefinition(FootnoteDefinition def, ReferencedDefinition refer renderBackrefs(def, referencedDefinition); html.tag("/p"); html.line(); + } else if (def instanceof InlineFootnote) { + html.tag("p", context.extendAttributes(def, "p", Map.of())); + renderChildren(def); + html.raw(" "); + renderBackrefs(def, referencedDefinition); + html.tag("/p"); + html.line(); } else { renderChildren(def); html.line(); @@ -223,7 +237,7 @@ private void renderDefinition(FootnoteDefinition def, ReferencedDefinition refer html.line(); } - private void renderBackrefs(FootnoteDefinition def, ReferencedDefinition referencedDefinition) { + private void renderBackrefs(Node def, ReferencedDefinition referencedDefinition) { var refs = referencedDefinition.references; for (int i = 0; i < refs.size(); i++) { var ref = refs.get(i); @@ -251,12 +265,22 @@ private void renderBackrefs(FootnoteDefinition def, ReferencedDefinition referen } } - private String referenceId(String label, int number) { - return "fnref-" + label + (number == 1 ? "" : ("-" + number)); + private String referenceId(String definitionKey, int number) { + return "fnref" + definitionKey + (number == 1 ? "" : ("-" + number)); } - private String definitionId(String label) { - return "fn-" + label; + private String definitionKey(String label, int number) { + // Named definitions use the pattern "fn-{name}" and inline definitions use "fn{number}" so as not to conflict. + // "fn{number}" is also what pandoc uses (for all types), starting with number 1. + if (label != null) { + return "-" + label; + } else { + return "" + number; + } + } + + private String definitionId(String definitionKey) { + return "fn" + definitionKey; } private void renderChildren(Node parent) { @@ -306,13 +330,18 @@ private static class ReferencedDefinition { * The definition number, starting from 1, and in order in which they're referenced. */ final int definitionNumber; + /** + * The unique key of the definition. Together with a static prefix it forms the ID used in the HTML. + */ + final String definitionKey; /** * The IDs of references for this definition, for backrefs. */ final List references = new ArrayList<>(); - ReferencedDefinition(int definitionNumber) { + ReferencedDefinition(int definitionNumber, String definitionKey) { this.definitionNumber = definitionNumber; + this.definitionKey = definitionKey; } } diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java index 8964e536c..68351083e 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java @@ -10,6 +10,7 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; +import java.util.List; import java.util.Set; public class FootnoteHtmlRendererTest extends RenderingTestCase { @@ -207,6 +208,23 @@ public void testNestedFootnotesUnreferenced() { "\n"); } + @Test + public void testInlineFootnotes() { + assertRenderingInline("Test ^[inline *footnote*]", + "

      Test 1

      \n" + + "
      \n" + + "
        \n" + + "
      1. \n" + + "

        inline footnote

        \n" + + "
      2. \n" + + "
      \n" + + "
      \n"); + + // TODO: Tricky ones like inline footnote referencing a normal one, and a definition containing an inline one. + // TODO: Nested inline footnotes? + // TODO: Inline and normal ones mixed, keys need to be unique + } + @Test public void testRenderNodesDirectly() { // Everything should work as expected when rendering from nodes directly (no parsing step). @@ -236,4 +254,11 @@ public void testRenderNodesDirectly() { protected String render(String source) { return RENDERER.render(PARSER.parse(source)); } + + private static void assertRenderingInline(String source, String expected) { + var extension = FootnotesExtension.builder().inlineFootnotes(true).build(); + var parser = Parser.builder().extensions(List.of(extension)).build(); + var renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); + Asserts.assertRendering(source, expected, renderer.render(parser.parse(source))); + } } From c6b4275288e30dd695b04a1779aaf1a0a2fccedc Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 11 Sep 2024 23:39:17 +1000 Subject: [PATCH 143/345] Inline footnotes rendering finished --- .../internal/FootnoteHtmlNodeRenderer.java | 51 ++++++++---- .../footnotes/FootnoteHtmlRendererTest.java | 81 ++++++++++++++++++- .../src/test/resources/footnotes.html | 18 +++++ 3 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 commonmark-ext-footnotes/src/test/resources/footnotes.html diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java index 2fe59b195..69a8c9fa0 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java @@ -67,7 +67,7 @@ public class FootnoteHtmlNodeRenderer implements NodeRenderer { * Information about references that should be rendered as footnotes. This doesn't contain all references, just the * ones from inside definitions. */ - private final Map references = new HashMap<>(); + private final Map references = new HashMap<>(); public FootnoteHtmlNodeRenderer(HtmlNodeRendererContext context) { this.html = context.getWriter(); @@ -91,6 +91,7 @@ public void beforeRoot(Node node) { public void render(Node node) { if (node instanceof FootnoteReference) { // This is called for all references, even ones inside definitions that we render at the end. + // Inside definitions, we have registered the reference already. var ref = (FootnoteReference) node; // Use containsKey because if value is null, we don't need to try registering again. var info = references.containsKey(ref) ? references.get(ref) : tryRegisterReference(ref); @@ -101,13 +102,16 @@ public void render(Node node) { html.text("[^" + ref.getLabel() + "]"); } } else if (node instanceof InlineFootnote) { - var info = registerReference(node, null); + var info = references.get(node); + if (info == null) { + info = registerReference(node, null); + } renderReference(node, info); } } @Override - public void afterRoot(Node node) { + public void afterRoot(Node rootNode) { // Now render the referenced definitions if there are any. if (referencedDefinitions.isEmpty()) { return; @@ -127,13 +131,19 @@ public void afterRoot(Node node) { var check = new LinkedList<>(referencedDefinitions.keySet()); while (!check.isEmpty()) { var def = check.removeFirst(); - def.accept(new ReferenceVisitor(ref -> { - var d = definitionMap.get(ref.getLabel()); - if (d != null) { - if (!referencedDefinitions.containsKey(d)) { - check.addLast(d); + def.accept(new ShallowReferenceVisitor(def, node -> { + if (node instanceof FootnoteReference) { + var ref = (FootnoteReference) node; + var d = definitionMap.get(ref.getLabel()); + if (d != null) { + if (!referencedDefinitions.containsKey(d)) { + check.addLast(d); + } + references.put(ref, registerReference(d, d.getLabel())); } - references.put(ref, registerReference(d, d.getLabel())); + } else if (node instanceof InlineFootnote) { + check.addLast(node); + references.put(node, registerReference(node, null)); } })); } @@ -307,18 +317,31 @@ public void visit(CustomBlock customBlock) { } } - private static class ReferenceVisitor extends AbstractVisitor { - private final Consumer consumer; + /** + * Visit footnote references/inline footnotes inside the parent (but not the parent itself). We want a shallow visit + * because the caller wants to control when to descend. + */ + private static class ShallowReferenceVisitor extends AbstractVisitor { + private final Node parent; + private final Consumer consumer; - private ReferenceVisitor(Consumer consumer) { + private ShallowReferenceVisitor(Node parent, Consumer consumer) { + this.parent = parent; this.consumer = consumer; } @Override public void visit(CustomNode customNode) { if (customNode instanceof FootnoteReference) { - var ref = (FootnoteReference) customNode; - consumer.accept(ref); + consumer.accept(customNode); + } else if (customNode instanceof InlineFootnote) { + if (customNode == parent) { + // Descend into the parent (inline footnotes can contain inline footnotes) + super.visit(customNode); + } else { + // Don't descend here because we want to be shallow. + consumer.accept(customNode); + } } else { super.visit(customNode); } diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java index 68351083e..553a3d8cc 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java @@ -221,10 +221,87 @@ public void testInlineFootnotes() { "\n"); // TODO: Tricky ones like inline footnote referencing a normal one, and a definition containing an inline one. - // TODO: Nested inline footnotes? - // TODO: Inline and normal ones mixed, keys need to be unique } + @Test + public void testInlineFootnotesNested() { + assertRenderingInline("Test ^[inline ^[nested]]", + "

      Test 1

      \n" + + "
      \n" + + "
        \n" + + "
      1. \n" + + "

        inline 2

        \n" + + "
      2. \n" + + "
      3. \n" + + "

        nested

        \n" + + "
      4. \n" + + "
      \n" + + "
      \n"); + } + + @Test + public void testInlineFootnoteWithReference() { + // This is a bit tricky because the IDs need to be unique. + assertRenderingInline("Test ^[inline [^1]]\n" + + "\n" + + "[^1]: normal", + "

      Test 1

      \n" + + "
      \n" + + "
        \n" + + "
      1. \n" + + "

        inline 2

        \n" + + "
      2. \n" + + "
      3. \n" + + "

        normal

        \n" + + "
      4. \n" + + "
      \n" + + "
      \n"); + } + + @Test + public void testInlineFootnoteInsideDefinition() { + assertRenderingInline("Test [^1]\n" + + "\n" + + "[^1]: Definition ^[inline]\n", + "

      Test 1

      \n" + + "
      \n" + + "
        \n" + + "
      1. \n" + + "

        Definition 2

        \n" + + "
      2. \n" + + "
      3. \n" + + "

        inline

        \n" + + "
      4. \n" + + "
      \n" + + "
      \n"); + } + + @Test + public void testInlineFootnoteInsideDefinition2() { + // Tricky because of the nested inline footnote which we want to visit after foo (breadth-first). + assertRenderingInline("Test [^1]\n" + + "\n" + + "[^1]: Definition ^[inline ^[nested]] ^[foo]\n", + "

      Test 1

      \n" + + "
      \n" + + "
        \n" + + "
      1. \n" + + "

        Definition 2 3

        \n" + + "
      2. \n" + + "
      3. \n" + + "

        inline 4

        \n" + + "
      4. \n" + + "
      5. \n" + + "

        foo

        \n" + + "
      6. \n" + + "
      7. \n" + + "

        nested

        \n" + + "
      8. \n" + + "
      \n" + + "
      \n"); + } + + @Test public void testRenderNodesDirectly() { // Everything should work as expected when rendering from nodes directly (no parsing step). diff --git a/commonmark-ext-footnotes/src/test/resources/footnotes.html b/commonmark-ext-footnotes/src/test/resources/footnotes.html new file mode 100644 index 000000000..1dd83185f --- /dev/null +++ b/commonmark-ext-footnotes/src/test/resources/footnotes.html @@ -0,0 +1,18 @@ + + + + + + Footnotes testing + + + + +Paste HTML from footnote rendering in here to manually check that linking works as expected. + + + From 0dfa8886538f4ba7f052233f5493a000a637044f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 11 Sep 2024 23:40:10 +1000 Subject: [PATCH 144/345] Rename param to allow implementations to use the normal node word --- .../ext/footnotes/internal/FootnoteHtmlNodeRenderer.java | 4 ++-- .../src/main/java/org/commonmark/renderer/NodeRenderer.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java index 69a8c9fa0..72c3792b1 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java @@ -80,10 +80,10 @@ public Set> getNodeTypes() { } @Override - public void beforeRoot(Node node) { + public void beforeRoot(Node rootNode) { // Collect all definitions first, so we can look them up when encountering a reference later. var visitor = new DefinitionVisitor(); - node.accept(visitor); + rootNode.accept(visitor); definitionMap = visitor.definitions; } diff --git a/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java index 193d5e267..304a4ef98 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java @@ -21,9 +21,9 @@ public interface NodeRenderer { */ void render(Node node); - default void beforeRoot(Node node) { + default void beforeRoot(Node rootNode) { } - default void afterRoot(Node node) { + default void afterRoot(Node rootNode) { } } From de53b0356b92421ceba022da302283d80aef78ff Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 12 Sep 2024 23:29:05 +1000 Subject: [PATCH 145/345] Remove old TODO --- .../org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java index 553a3d8cc..ce5fdd51a 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java @@ -219,8 +219,6 @@ public void testInlineFootnotes() { "\n" + "
    \n" + "\n"); - - // TODO: Tricky ones like inline footnote referencing a normal one, and a definition containing an inline one. } @Test From e3e38ef8f87348017f9767f9252a214625ebbab2 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 12 Sep 2024 23:29:18 +1000 Subject: [PATCH 146/345] Javadoc --- .../java/org/commonmark/renderer/NodeRenderer.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java index 304a4ef98..4ae4b5dcd 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java @@ -21,9 +21,19 @@ public interface NodeRenderer { */ void render(Node node); + /** + * Called before the root node is rendered, to do any initial processing at the start. + * + * @param rootNode the root (top-level) node + */ default void beforeRoot(Node rootNode) { } + /** + * Called after the root node is rendered, to do any final processing at the end. + * + * @param rootNode the root (top-level) node + */ default void afterRoot(Node rootNode) { } } From e3795b27a9d200b16f2fbb3b7249ba196a0c10cb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 12 Sep 2024 23:38:30 +1000 Subject: [PATCH 147/345] footnotes: Add Markdown rendering for inline footnotes --- .../internal/FootnoteMarkdownNodeRenderer.java | 11 ++++++++++- .../ext/footnotes/FootnoteMarkdownRendererTest.java | 7 ++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java index 11b315386..342fb7ebb 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java @@ -2,6 +2,7 @@ import org.commonmark.ext.footnotes.FootnoteDefinition; import org.commonmark.ext.footnotes.FootnoteReference; +import org.commonmark.ext.footnotes.InlineFootnote; import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.HtmlNodeRendererContext; @@ -26,13 +27,15 @@ public FootnoteMarkdownNodeRenderer(MarkdownNodeRendererContext context) { @Override public Set> getNodeTypes() { - return Set.of(FootnoteReference.class, FootnoteDefinition.class); + return Set.of(FootnoteReference.class, InlineFootnote.class, FootnoteDefinition.class); } @Override public void render(Node node) { if (node instanceof FootnoteReference) { renderReference((FootnoteReference) node); + } else if (node instanceof InlineFootnote) { + renderInline((InlineFootnote) node); } else if (node instanceof FootnoteDefinition) { renderDefinition((FootnoteDefinition) node); } @@ -45,6 +48,12 @@ private void renderReference(FootnoteReference ref) { writer.raw("]"); } + private void renderInline(InlineFootnote inlineFootnote) { + writer.raw("^["); + renderChildren(inlineFootnote); + writer.raw("]"); + } + private void renderDefinition(FootnoteDefinition def) { writer.raw("[^"); writer.raw(def.getLabel()); diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java index 2ab2ba0eb..9d567ece2 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java @@ -11,7 +11,7 @@ import static org.junit.Assert.assertEquals; public class FootnoteMarkdownRendererTest { - private static final Set EXTENSIONS = Set.of(FootnotesExtension.create()); + private static final Set EXTENSIONS = Set.of(FootnotesExtension.builder().inlineFootnotes(true).build()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); @@ -36,6 +36,11 @@ public void testBackslashInLabel() { assertRoundTrip("[^\\foo]\n\n[^\\foo]: note\n"); } + @Test + public void testInline() { + assertRoundTrip("^[test *foo*]\n"); + } + private void assertRoundTrip(String input) { String rendered = parseAndRender(input); assertEquals(input, rendered); From 26d4df9851c7f311d4ba5af7c0c00c054588c559 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 13 Sep 2024 00:02:16 +1000 Subject: [PATCH 148/345] Don't escape `=` text if it's the first node in a block Fixes #335 --- .../renderer/markdown/CoreMarkdownNodeRenderer.java | 8 +++++--- .../renderer/markdown/MarkdownRendererTest.java | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 8a9e57251..8b171d1c9 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -366,9 +366,11 @@ public void visit(Text text) { break; } case '=': { - // Would be ambiguous with a Setext heading, escape - writer.raw("\\="); - literal = literal.substring(1); + // Would be ambiguous with a Setext heading, escape unless it's the first line in the block + if (text.getPrevious() != null) { + writer.raw("\\="); + literal = literal.substring(1); + } break; } case '0': diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 91af1bfe8..7917d1197 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -189,6 +189,9 @@ public void testEscaping() { assertRoundTrip("\\## Test\n"); assertRoundTrip("\\#\n"); assertRoundTrip("Foo\n\\===\n"); + // Only needs to be escaped after some text, not at beginning of paragraph + assertRoundTrip("===\n"); + assertRoundTrip("a\n\n===\n"); // The beginning of the line within the block, so disregarding prefixes assertRoundTrip("> \\- Test\n"); assertRoundTrip("- \\- Test\n"); From 7be42049400d8ce5db9d64e9c73da5a097dc1e22 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 14 Sep 2024 18:45:53 +1000 Subject: [PATCH 149/345] Fix source spans of blocks with lazy continuation lines Fixes #337. --- .../commonmark/internal/DocumentParser.java | 10 ++-- .../org/commonmark/test/SourceSpansTest.java | 60 +++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 107001742..d3f6f6811 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -288,7 +288,7 @@ private void parseLine(String ln) { // What remains at the offset is a text line. Add the text to the // appropriate block. - // First check for a lazy paragraph continuation: + // First check for a lazy continuation line if (!startedNewBlock && !isBlank() && getActiveBlockParser().canHaveLazyContinuationLines()) { openBlockParsers.get(openBlockParsers.size() - 1).sourceIndex = lastIndex; @@ -441,10 +441,12 @@ private void addLine() { private void addSourceSpans() { if (includeSourceSpans != IncludeSourceSpans.NONE) { - // Don't add source spans for Document itself (it would get the whole source text) + // Don't add source spans for Document itself (it would get the whole source text), so start at 1, not 0 for (int i = 1; i < openBlockParsers.size(); i++) { - OpenBlockParser openBlockParser = openBlockParsers.get(i); - int blockIndex = openBlockParser.sourceIndex; + var openBlockParser = openBlockParsers.get(i); + // In case of a lazy continuation line, the index is less than where the block parser would expect the + // contents to start, so let's use whichever is smaller. + int blockIndex = Math.min(openBlockParser.sourceIndex, index); int length = line.getContent().length() - blockIndex; if (length != 0) { openBlockParser.blockParser.addSourceSpan(SourceSpan.of(lineIndex, blockIndex, length)); diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index 8eb6260d0..eb7e8f005 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -204,6 +204,66 @@ public void linkReferenceDefinitionHeading() { assertEquals(List.of(SourceSpan.of(1, 0, 7), SourceSpan.of(2, 0, 3)), heading.getSourceSpans()); } + @Test + public void lazyContinuationLines() { + { + // From https://spec.commonmark.org/0.31.2/#example-250 + // Wrong source span for the inner block quote for the second line. + var doc = PARSER.parse("> > > foo\nbar\n"); + + var bq1 = (BlockQuote) doc.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 0, 9), SourceSpan.of(1, 0, 3)), bq1.getSourceSpans()); + var bq2 = (BlockQuote) bq1.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 2, 7), SourceSpan.of(1, 0, 3)), bq2.getSourceSpans()); + var bq3 = (BlockQuote) bq2.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 4, 5), SourceSpan.of(1, 0, 3)), bq3.getSourceSpans()); + var paragraph = (Paragraph) bq3.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 6, 3), SourceSpan.of(1, 0, 3)), paragraph.getSourceSpans()); + } + + { + // Adding one character to the last line remove blockQuote3 source for the second line + var doc = PARSER.parse("> > > foo\nbars\n"); + + var bq1 = (BlockQuote) doc.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 0, 9), SourceSpan.of(1, 0, 4)), bq1.getSourceSpans()); + var bq2 = (BlockQuote) bq1.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 2, 7), SourceSpan.of(1, 0, 4)), bq2.getSourceSpans()); + var bq3 = (BlockQuote) bq2.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 4, 5), SourceSpan.of(1, 0, 4)), bq3.getSourceSpans()); + var paragraph = (Paragraph) bq3.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 6, 3), SourceSpan.of(1, 0, 4)), paragraph.getSourceSpans()); + } + + { + // From https://spec.commonmark.org/0.31.2/#example-292 + var doc = PARSER.parse("> 1. > Blockquote\ncontinued here."); + + var bq1 = (BlockQuote) doc.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 0, 17), SourceSpan.of(1, 0, 15)), bq1.getSourceSpans()); + var orderedList = (OrderedList) bq1.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 2, 15), SourceSpan.of(1, 0, 15)), orderedList.getSourceSpans()); + var listItem = (ListItem) orderedList.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 2, 15), SourceSpan.of(1, 0, 15)), listItem.getSourceSpans()); + var bq2 = (BlockQuote) listItem.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 5, 12), SourceSpan.of(1, 0, 15)), bq2.getSourceSpans()); + var paragraph = (Paragraph) bq2.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 7, 10), SourceSpan.of(1, 0, 15)), paragraph.getSourceSpans()); + } + + { + // Lazy continuation line for nested blockquote + var doc = PARSER.parse("> > foo\n> bar\n"); + + var bq1 = (BlockQuote) doc.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 5)), bq1.getSourceSpans()); + var bq2 = (BlockQuote) bq1.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 2, 5), SourceSpan.of(1, 2, 3)), bq2.getSourceSpans()); + var paragraph = (Paragraph) bq2.getLastChild(); + assertEquals(List.of(SourceSpan.of(0, 4, 3), SourceSpan.of(1, 2, 3)), paragraph.getSourceSpans()); + } + } + @Test public void visualCheck() { assertEquals("(> {[* ]})\n(> {[ ]})\n(> {⸢* ⸤baz⸥⸣})\n", From 8c819ab7779acbe99ce4d9b314745c39f22314e9 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 14 Sep 2024 22:14:56 +1000 Subject: [PATCH 150/345] Add omitSingleParagraphP option to HtmlRenderer.Builder This is useful for rendering single lines where wrapping in a

    might be undesirable. Fixes #150. --- .../renderer/html/CoreHtmlNodeRenderer.java | 8 ++++--- .../html/HtmlNodeRendererContext.java | 9 ++++++-- .../renderer/html/HtmlRenderer.java | 23 +++++++++++++++++-- .../org/commonmark/test/HtmlRendererTest.java | 8 +++++++ 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java index 115d553f0..0603aa013 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -69,13 +69,15 @@ public void visit(Heading heading) { @Override public void visit(Paragraph paragraph) { - boolean inTightList = isInTightList(paragraph); - if (!inTightList) { + boolean omitP = isInTightList(paragraph) || // + (context.shouldOmitSingleParagraphP() && paragraph.getParent() instanceof Document && // + paragraph.getPrevious() == null && paragraph.getNext() == null); + if (!omitP) { html.line(); html.tag("p", getAttrs(paragraph, "p")); } visitChildren(paragraph); - if (!inTightList) { + if (!omitP) { html.tag("/p"); html.line(); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java index eb950ffa6..eecff0f44 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java @@ -17,8 +17,8 @@ public interface HtmlNodeRendererContext { /** * Let extensions modify the HTML tag attributes. * - * @param node the node for which the attributes are applied - * @param tagName the HTML tag name that these attributes are for (e.g. {@code h1}, {@code pre}, {@code code}). + * @param node the node for which the attributes are applied + * @param tagName the HTML tag name that these attributes are for (e.g. {@code h1}, {@code pre}, {@code code}). * @param attributes the attributes that were calculated by the renderer * @return the extended attributes with added/updated/removed entries */ @@ -47,6 +47,11 @@ public interface HtmlNodeRendererContext { */ boolean shouldEscapeHtml(); + /** + * @return whether documents that only contain a single paragraph should be rendered without the {@code

    } tag + */ + boolean shouldOmitSingleParagraphP(); + /** * @return true if the {@link UrlSanitizer} should be used. * @since 0.14.0 diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java index 35db46a64..386abebf0 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java @@ -22,17 +22,19 @@ public class HtmlRenderer implements Renderer { private final String softbreak; private final boolean escapeHtml; + private final boolean percentEncodeUrls; + private final boolean omitSingleParagraphP; private final boolean sanitizeUrls; private final UrlSanitizer urlSanitizer; - private final boolean percentEncodeUrls; private final List attributeProviderFactories; private final List nodeRendererFactories; private HtmlRenderer(Builder builder) { this.softbreak = builder.softbreak; this.escapeHtml = builder.escapeHtml; - this.sanitizeUrls = builder.sanitizeUrls; this.percentEncodeUrls = builder.percentEncodeUrls; + this.omitSingleParagraphP = builder.omitSingleParagraphP; + this.sanitizeUrls = builder.sanitizeUrls; this.urlSanitizer = builder.urlSanitizer; this.attributeProviderFactories = new ArrayList<>(builder.attributeProviderFactories); @@ -83,6 +85,7 @@ public static class Builder { private boolean sanitizeUrls = false; private UrlSanitizer urlSanitizer = new DefaultUrlSanitizer(); private boolean percentEncodeUrls = false; + private boolean omitSingleParagraphP = false; private List attributeProviderFactories = new ArrayList<>(); private List nodeRendererFactories = new ArrayList<>(); @@ -166,6 +169,17 @@ public Builder percentEncodeUrls(boolean percentEncodeUrls) { return this; } + /** + * Whether documents that only contain a single paragraph should be rendered without the {@code

    } tag. Set to + * {@code true} to render without the tag; the default of {@code false} always renders the tag. + * + * @return {@code this} + */ + public Builder omitSingleParagraphP(boolean omitSingleParagraphP) { + this.omitSingleParagraphP = omitSingleParagraphP; + return this; + } + /** * Add a factory for an attribute provider for adding/changing HTML attributes to the rendered tags. * @@ -242,6 +256,11 @@ public boolean shouldEscapeHtml() { return escapeHtml; } + @Override + public boolean shouldOmitSingleParagraphP() { + return omitSingleParagraphP; + } + @Override public boolean shouldSanitizeUrls() { return sanitizeUrls; diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 7cc0b036a..018b9e453 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -4,6 +4,8 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.*; +import org.commonmark.testutil.Asserts; +import org.commonmark.testutil.RenderingTestCase; import org.commonmark.testutil.TestResources; import org.junit.Test; @@ -308,6 +310,12 @@ public void canRenderContentsOfSingleParagraph() { defaultRenderer().render(document)); } + @Test + public void omitSingleParagraphP() { + var renderer = HtmlRenderer.builder().omitSingleParagraphP(true).build(); + assertEquals("hi there", renderer.render(parse("hi *there*"))); + } + @Test public void threading() throws Exception { Parser parser = Parser.builder().build(); From 369dbab57f7d29502036ffe963b20afe90ff7239 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 16 Sep 2024 23:10:29 +1000 Subject: [PATCH 151/345] Prepare CHANGELOG for release 0.23 --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b4afae55..f98fe97d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,25 +6,43 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. -## Unreleased +## [0.23.0] - 2024-09-16 ### Added -- Support for extending inline parsing with custom inline content parsers! See - `Parser.Builder#customInlineContentParserFactory`. This allows users or - extensions to hook into inline parsing on a deeper level than using delimiter - processors. It could be used to implement support for math/latex formulas for - example. +- New extension for footnotes! + - Syntax: + ``` + Main text[^1] + + [^1]: Additional text in a footnote + ``` + - Inline footnotes like `^[inline footnote]` are also supported when enabled + via an option in `FootnotesExtension.Builder` + - Use class `FootnotesExtension` in artifact `commonmark-ext-footnotes` (#332) +- New option `omitSingleParagraphP` in `HtmlRenderer.Builder` for not using `

    ` + tags for when a document only has one paragraph (#150) +- Support for custom link processing during inline parsing (e.g. `[foo]`), + see `Parser.Builder#linkProcessor` +- Support for extending inline parsing with custom inline content parsers. See + `Parser.Builder#customInlineContentParserFactory`. This allows users/extensions + to hook into inline parsing on a deeper level than before (e.g. with delimiter + processors). It can be used to add support for math/latex formulas or other inline + syntax. (#321) ### Changed +- The default `DefaultUrlSanitizer` now also allows `data` as a protocol. Use the + constructor with a list to customize this. (#329) - `LinkReferenceDefinition` now extends `Block` (it was extending `Node` directly before) +- `MarkdownRenderer`: Don't escape `=` text if it's the first node in a block (#335) ### Fixed -- Fix parsing of link reference definitions where it looks like it has a title - but it doesn't because it's followed by characters other than space/tab. In that - case, the title was set to the partially-parsed title and the source spans were - wrong (#315). +- Fix parsing of link reference definitions with incorrect title syntax (followed + by characters other than space/tab). In that case, the title was set to the + partially-parsed title and the source spans were wrong. (#315) +- Fix source spans of blocks with lazy continuation lines (#337) +- `MarkdownRenderer`: Preserve thematic break literals (#331) ## [0.22.0] - 2024-03-15 ### Added -- New `MarkdownRenderer` for rendering nodes to Markdown (CommonMark)! +- New `MarkdownRenderer` for rendering nodes to Markdown (CommonMark) (#306)! Note that while care is taken to produce equivalent Markdown, some differences in the original Markdown (if parsed) are not preserved, such as: - The type of heading used @@ -397,7 +415,7 @@ API breaking changes (caused by changes in spec): - Rename `HorizontalRule` to `ThematicBreak` - Rename `HtmlTag` to `HtmlInline` - Replace `MatchedBlockParser#getParagraphStartLine` with `#getParagraphContent` - that returns the current content if the the matched block is a paragraph + that returns the current content if the matched block is a paragraph ## [0.3.2] - 2016-01-07 ### Fixed @@ -427,6 +445,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.23.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.22.0...commonmark-parent-0.23.0 [0.22.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.21.0...commonmark-parent-0.22.0 [0.21.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.20.0...commonmark-parent-0.21.0 [0.20.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.19.0...commonmark-parent-0.20.0 From bc231e2457cd22683218d34b93f0fe7f768138c2 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 16 Sep 2024 23:11:59 +1000 Subject: [PATCH 152/345] Prepare for version 0.23.0 Ran `mvn versions:set -DnewVersion=0.23.0-SNAPSHOT`` --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 22 +++++++++++----------- 13 files changed, 23 insertions(+), 23 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 8bd242049..5fbbf0dbc 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index f70b7d262..f5b87fb3a 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index b119b57f8..1529413e0 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 5c031287e..7c29c17c8 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index a7b16ef1c..0f82a822c 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index ed654760f..bb0e65961 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 2e00c6c16..6db2a5564 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 68b56e950..682eb0b62 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 7ecf7336d..15a564d5e 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index bbd4c8a74..26b1a6d1c 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index dd115fdad..0ce2ecdac 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index d47260296..7bc0cd8d5 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 32003fdf7..c4b87b7be 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ org.commonmark commonmark - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT org.commonmark commonmark-ext-ins - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT org.commonmark commonmark-test-util - 0.22.1-SNAPSHOT + 0.23.0-SNAPSHOT From 59aa908e4c7e3006929201d3ad697de887d0d9eb Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Mon, 16 Sep 2024 14:01:00 +0000 Subject: [PATCH 153/345] [maven-release-plugin] prepare release commonmark-parent-0.23.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 24 ++++++++++++------------ 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 5fbbf0dbc..fa1f893e4 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0-SNAPSHOT + 0.23.0 commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index f5b87fb3a..5e8dea19a 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0-SNAPSHOT + 0.23.0 commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 1529413e0..2bdb03eb2 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0-SNAPSHOT + 0.23.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 7c29c17c8..cd67ad31c 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0-SNAPSHOT + 0.23.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 0f82a822c..b12609770 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0-SNAPSHOT + 0.23.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index bb0e65961..ca73e9cb8 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0-SNAPSHOT + 0.23.0 commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 6db2a5564..e3d5453ff 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0-SNAPSHOT + 0.23.0 commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 682eb0b62..36581a2fe 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0-SNAPSHOT + 0.23.0 commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 15a564d5e..ba54fe543 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.23.0-SNAPSHOT + 0.23.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 26b1a6d1c..d864cec42 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0-SNAPSHOT + 0.23.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 0ce2ecdac..22bb0333e 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0-SNAPSHOT + 0.23.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 7bc0cd8d5..aec8385dc 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0-SNAPSHOT + 0.23.0 commonmark diff --git a/pom.xml b/pom.xml index c4b87b7be..459d686a5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.23.0-SNAPSHOT + 0.23.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ org.commonmark commonmark - 0.23.0-SNAPSHOT + 0.23.0 org.commonmark commonmark-ext-autolink - 0.23.0-SNAPSHOT + 0.23.0 org.commonmark commonmark-ext-image-attributes - 0.23.0-SNAPSHOT + 0.23.0 org.commonmark commonmark-ext-ins - 0.23.0-SNAPSHOT + 0.23.0 org.commonmark commonmark-ext-gfm-strikethrough - 0.23.0-SNAPSHOT + 0.23.0 org.commonmark commonmark-ext-gfm-tables - 0.23.0-SNAPSHOT + 0.23.0 org.commonmark commonmark-ext-heading-anchor - 0.23.0-SNAPSHOT + 0.23.0 org.commonmark commonmark-ext-task-list-items - 0.23.0-SNAPSHOT + 0.23.0 org.commonmark commonmark-ext-yaml-front-matter - 0.23.0-SNAPSHOT + 0.23.0 org.commonmark commonmark-test-util - 0.23.0-SNAPSHOT + 0.23.0 @@ -279,7 +279,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - HEAD + commonmark-parent-0.23.0 From 0ea7634383eeafd2f714f43eb15824c092ea82d8 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Mon, 16 Sep 2024 14:01:02 +0000 Subject: [PATCH 154/345] [maven-release-plugin] prepare for next development iteration --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 24 ++++++++++++------------ 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index fa1f893e4..9e879640f 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0 + 0.23.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 5e8dea19a..36204b775 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0 + 0.23.1-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 2bdb03eb2..40c234e88 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0 + 0.23.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index cd67ad31c..06e828240 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0 + 0.23.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index b12609770..8998cb4c1 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0 + 0.23.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index ca73e9cb8..c706a1fe6 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0 + 0.23.1-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index e3d5453ff..277dad07c 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0 + 0.23.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 36581a2fe..1a42059dc 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0 + 0.23.1-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index ba54fe543..079aaf46c 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.23.0 + 0.23.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index d864cec42..e82661ed4 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0 + 0.23.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 22bb0333e..a8c8c73ad 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0 + 0.23.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index aec8385dc..3d4ddc2b7 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.0 + 0.23.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 459d686a5..dc1aa344d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.23.0 + 0.23.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ org.commonmark commonmark - 0.23.0 + 0.23.1-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.23.0 + 0.23.1-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.23.0 + 0.23.1-SNAPSHOT org.commonmark commonmark-ext-ins - 0.23.0 + 0.23.1-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.23.0 + 0.23.1-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.23.0 + 0.23.1-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.23.0 + 0.23.1-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.23.0 + 0.23.1-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.23.0 + 0.23.1-SNAPSHOT org.commonmark commonmark-test-util - 0.23.0 + 0.23.1-SNAPSHOT @@ -279,7 +279,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - commonmark-parent-0.23.0 + HEAD From ddf51f3f8051248828cd14eddb54c3a046c940f8 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 18 Sep 2024 18:24:02 +1000 Subject: [PATCH 155/345] README: Bump version, add new APIs --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3131e2182..95e5b5590 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.22.0 + 0.23.0 ``` @@ -233,6 +233,7 @@ all of them via methods on `Parser.Builder` - Parsing of inline content can be extended/overridden with `customInlineContentParserFactory` - Parsing of [delimiters](https://spec.commonmark.org/0.31.2/#emphasis-and-strong-emphasis) in inline content can be extended with `customDelimiterProcessor` +- Processing of links can be customized with `linkProcessor` and `linkMarker` #### Thread-safety @@ -265,7 +266,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.22.0 + 0.23.0 ``` @@ -307,6 +308,21 @@ Enables tables using pipes as in [GitHub Flavored Markdown][gfm-tables]. Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. +### Footnotes + +Enables footnotes like in [GitHub](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#footnotes) +or [Pandoc](https://pandoc.org/MANUAL.html#footnotes): + +``` +Main text[^1] + +[^1]: Additional text in a footnote +``` + +Inline footnotes like `^[inline footnote]` are also supported when enabled via `FootnotesExtension.Builder#inlineFootnotes`. + +Use class `FootnotesExtension` in artifact `commonmark-ext-footnotes`. + ### Heading anchor Enables adding auto generated "id" attributes to heading tags. The "id" From 005b5894b1fce43f3fffe07e9bcaa91bb764cd61 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 18 Sep 2024 19:00:36 +1000 Subject: [PATCH 156/345] Update Javadoc --- commonmark/src/main/java/org/commonmark/parser/Parser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 150f3aaf9..4d534cf72 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -187,7 +187,7 @@ public Builder enabledBlockTypes(Set> enabledBlockTypes) } /** - * Whether to calculate {@link org.commonmark.node.SourceSpan} for {@link Node}. + * Whether to calculate source positions for parsed {@link Node Nodes}, see {@link Node#getSourceSpans()}. *

    * By default, source spans are disabled. * From 355a1239f49d762d0d73d5d6270acb4e56715723 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 18 Sep 2024 19:36:01 +1000 Subject: [PATCH 157/345] Update copyright --- LICENSE.txt | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index b09e367ce..604b777d3 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2015, Atlassian Pty Ltd +Copyright (c) 2015, Robin Stocker All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 95e5b5590..08b197e16 100644 --- a/README.md +++ b/README.md @@ -430,7 +430,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) file. License ------- -Copyright (c) Atlassian and others. +Copyright (c) 2015, Robin Stocker BSD (2-clause) licensed, see LICENSE.txt file. From 8ce24de9f9134a5bee25020d136e244405ec777b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 18 Sep 2024 19:36:08 +1000 Subject: [PATCH 158/345] README: Add "Used by" section --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 08b197e16..7ab691e6c 100644 --- a/README.md +++ b/README.md @@ -416,6 +416,14 @@ You can also find other extensions in the wild: * [commonmark-ext-notifications](https://github.com/McFoggy/commonmark-ext-notifications): this extension allows to easily create notifications/admonitions paragraphs like `INFO`, `SUCCESS`, `WARNING` or `ERROR` +Used by +------- + +Some users of this library (feel free to raise a PR if you want to be added): +* Atlassian (where the library was initially developed) +* Java (OpenJDK), see [here](https://github.com/openjdk/jdk/blob/3895b8fc0b2c6d187080dba6fe08297adad4a480/src/jdk.internal.md/share/classes/module-info.java) +* Gitiles/Gerrit, see [here](https://gerrit-review.googlesource.com/c/gitiles/+/353794) + See also -------- From 45950d4a95f4ae9740c32377c5c2b821ee9b73cd Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 20 Sep 2024 14:46:05 +1000 Subject: [PATCH 159/345] Add missing break to switch statement --- .../src/main/java/org/commonmark/internal/HeadingParser.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index d422c1241..7b6e7c326 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -140,10 +140,12 @@ private static int getSetextHeadingLevel(CharSequence line, int index) { if (isSetextHeadingRest(line, index + 1, '=')) { return 1; } + break; case '-': if (isSetextHeadingRest(line, index + 1, '-')) { return 2; } + break; } return 0; } From 6f2572bda43415c47bfff20846b3afc0433915cc Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 20 Sep 2024 14:56:12 +1000 Subject: [PATCH 160/345] Replace Unicode characters in sources with escapes --- .../ext/footnotes/internal/FootnoteHtmlNodeRenderer.java | 2 +- .../main/java/org/commonmark/internal/HeadingParser.java | 2 +- .../java/org/commonmark/internal/ListBlockParser.java | 2 +- .../main/java/org/commonmark/internal/util/Escaping.java | 8 ++++---- .../renderer/text/CoreTextContentNodeRenderer.java | 6 ++++-- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java index 72c3792b1..70eb048a3 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java @@ -267,7 +267,7 @@ private void renderBackrefs(Node def, ReferencedDefinition referencedDefinition) html.tag("/sup"); } // U+21A9 LEFTWARDS ARROW WITH HOOK - html.raw("↩"); + html.raw("\u21A9"); html.tag("/a"); if (i + 1 < refs.size()) { html.raw(" "); diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index 7b6e7c326..3bc9ba5c4 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -69,7 +69,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } // spec: An ATX heading consists of a string of characters, parsed as inline content, between an opening sequence of - // 1–6 unescaped # characters and an optional closing sequence of any number of unescaped # characters. The opening + // 1-6 unescaped # characters and an optional closing sequence of any number of unescaped # characters. The opening // sequence of # characters must be followed by a space or by the end of line. The optional closing sequence of #s // must be preceded by a space and may be followed by spaces only. private static HeadingParser getAtxHeading(SourceLine line) { diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index d77744da7..fbf034757 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -128,7 +128,7 @@ private static ListMarkerData parseListMarker(CharSequence line, int index) { } } - // spec: An ordered list marker is a sequence of 1–9 arabic digits (0-9), followed by either a `.` character or a + // spec: An ordered list marker is a sequence of 1-9 arabic digits (0-9), followed by either a `.` character or a // `)` character. private static ListMarkerData parseOrderedList(CharSequence line, int index) { int digits = 0; diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java index 27f59ebf7..d928b6f43 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java @@ -115,11 +115,11 @@ public static String percentEncodeUrl(String s) { public static String normalizeLabelContent(String input) { String trimmed = input.trim(); - // This is necessary to correctly case fold "ẞ" to "SS": - // "ẞ".toLowerCase(Locale.ROOT) -> "ß" - // "ß".toUpperCase(Locale.ROOT) -> "SS" + // This is necessary to correctly case fold "\u1E9E" (LATIN CAPITAL LETTER SHARP S) to "SS": + // "\u1E9E".toLowerCase(Locale.ROOT) -> "\u00DF" (LATIN SMALL LETTER SHARP S) + // "\u00DF".toUpperCase(Locale.ROOT) -> "SS" // Note that doing upper first (or only upper without lower) wouldn't work because: - // "ẞ".toUpperCase(Locale.ROOT) -> "ẞ" + // "\u1E9E".toUpperCase(Locale.ROOT) -> "\u1E9E" String caseFolded = trimmed.toLowerCase(Locale.ROOT).toUpperCase(Locale.ROOT); return WHITESPACE.matcher(caseFolded).replaceAll(" "); diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java index 6dcc9d1eb..865533804 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -62,9 +62,11 @@ public void visit(Document document) { @Override public void visit(BlockQuote blockQuote) { - textContent.write('«'); + // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + textContent.write('\u00AB'); visitChildren(blockQuote); - textContent.write('»'); + // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + textContent.write('\u00BB'); writeEndOfLineIfNeeded(blockQuote, null); } From 1dacdcb1e32911bbf0935f9613f6c34824b3e358 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 20 Sep 2024 14:58:05 +1000 Subject: [PATCH 161/345] Rename entities.properties to entities.txt --- .../main/java/org/commonmark/internal/util/Html5Entities.java | 2 +- .../internal/util/{entities.properties => entities.txt} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename commonmark/src/main/resources/org/commonmark/internal/util/{entities.properties => entities.txt} (100%) diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java b/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java index 523c596ed..8da53c053 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java @@ -12,7 +12,7 @@ public class Html5Entities { private static final Map NAMED_CHARACTER_REFERENCES = readEntities(); - private static final String ENTITY_PATH = "/org/commonmark/internal/util/entities.properties"; + private static final String ENTITY_PATH = "/org/commonmark/internal/util/entities.txt"; public static String entityToString(String input) { if (!input.startsWith("&") || !input.endsWith(";")) { diff --git a/commonmark/src/main/resources/org/commonmark/internal/util/entities.properties b/commonmark/src/main/resources/org/commonmark/internal/util/entities.txt similarity index 100% rename from commonmark/src/main/resources/org/commonmark/internal/util/entities.properties rename to commonmark/src/main/resources/org/commonmark/internal/util/entities.txt From 42a025f24b4e5b424a913be2667413a649c7938e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 23 Sep 2024 22:39:05 +1000 Subject: [PATCH 162/345] TextContentRenderer: Replace stripNewlines with lineBreakRendering --- .../text/CoreTextContentNodeRenderer.java | 105 ++++++++---------- .../renderer/text/LineBreakRendering.java | 19 ++++ .../text/TextContentNodeRendererContext.java | 9 ++ .../renderer/text/TextContentRenderer.java | 30 ++++- .../renderer/text/TextContentWriter.java | 66 +++++++++-- .../test/TextContentRendererTest.java | 3 +- 6 files changed, 159 insertions(+), 73 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/renderer/text/LineBreakRendering.java diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java index 865533804..64968ae76 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -65,25 +65,20 @@ public void visit(BlockQuote blockQuote) { // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK textContent.write('\u00AB'); visitChildren(blockQuote); + textContent.resetBlock(); // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK textContent.write('\u00BB'); - writeEndOfLineIfNeeded(blockQuote, null); + textContent.block(); } @Override public void visit(BulletList bulletList) { - if (listHolder != null) { - writeEndOfLine(); - } + // TODO: isTight() listHolder = new BulletListHolder(listHolder, bulletList); visitChildren(bulletList); - writeEndOfLineIfNeeded(bulletList, null); - if (listHolder.getParent() != null) { - listHolder = listHolder.getParent(); - } else { - listHolder = null; - } + textContent.block(); + listHolder = listHolder.getParent(); } @Override @@ -95,31 +90,40 @@ public void visit(Code code) { @Override public void visit(FencedCodeBlock fencedCodeBlock) { - if (context.stripNewlines()) { - textContent.writeStripped(fencedCodeBlock.getLiteral()); - writeEndOfLineIfNeeded(fencedCodeBlock, null); + var literal = stripTrailingNewline(fencedCodeBlock.getLiteral()); + if (stripNewlines()) { + textContent.writeStripped(literal); } else { - textContent.write(fencedCodeBlock.getLiteral()); + textContent.write(literal); } + textContent.block(); } @Override public void visit(HardLineBreak hardLineBreak) { - writeEndOfLineIfNeeded(hardLineBreak, null); + if (stripNewlines()) { + textContent.whitespace(); + } else { + textContent.line(); + } } @Override public void visit(Heading heading) { visitChildren(heading); - writeEndOfLineIfNeeded(heading, ':'); + if (stripNewlines()) { + textContent.write(": "); + } else { + textContent.block(); + } } @Override public void visit(ThematicBreak thematicBreak) { - if (!context.stripNewlines()) { + if (!stripNewlines()) { textContent.write("***"); } - writeEndOfLineIfNeeded(thematicBreak, null); + textContent.block(); } @Override @@ -139,12 +143,13 @@ public void visit(Image image) { @Override public void visit(IndentedCodeBlock indentedCodeBlock) { - if (context.stripNewlines()) { - textContent.writeStripped(indentedCodeBlock.getLiteral()); - writeEndOfLineIfNeeded(indentedCodeBlock, null); + var literal = stripTrailingNewline(indentedCodeBlock.getLiteral()); + if (stripNewlines()) { + textContent.writeStripped(literal); } else { - textContent.write(indentedCodeBlock.getLiteral()); + textContent.write(literal); } + textContent.block(); } @Override @@ -156,48 +161,43 @@ public void visit(Link link) { public void visit(ListItem listItem) { if (listHolder != null && listHolder instanceof OrderedListHolder) { OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; - String indent = context.stripNewlines() ? "" : orderedListHolder.getIndent(); + String indent = stripNewlines() ? "" : orderedListHolder.getIndent(); textContent.write(indent + orderedListHolder.getCounter() + orderedListHolder.getDelimiter() + " "); visitChildren(listItem); - writeEndOfLineIfNeeded(listItem, null); + textContent.block(); orderedListHolder.increaseCounter(); } else if (listHolder != null && listHolder instanceof BulletListHolder) { BulletListHolder bulletListHolder = (BulletListHolder) listHolder; - if (!context.stripNewlines()) { + if (!stripNewlines()) { textContent.write(bulletListHolder.getIndent() + bulletListHolder.getMarker() + " "); } visitChildren(listItem); - writeEndOfLineIfNeeded(listItem, null); + textContent.block(); } } @Override public void visit(OrderedList orderedList) { - if (listHolder != null) { - writeEndOfLine(); - } + // TODO: isTight() listHolder = new OrderedListHolder(listHolder, orderedList); visitChildren(orderedList); - writeEndOfLineIfNeeded(orderedList, null); - if (listHolder.getParent() != null) { - listHolder = listHolder.getParent(); - } else { - listHolder = null; - } + textContent.block(); + listHolder = listHolder.getParent(); } @Override public void visit(Paragraph paragraph) { visitChildren(paragraph); - // Add "end of line" only if its "root paragraph. - if (paragraph.getParent() == null || paragraph.getParent() instanceof Document) { - writeEndOfLineIfNeeded(paragraph, null); - } + textContent.block(); } @Override public void visit(SoftLineBreak softLineBreak) { - writeEndOfLineIfNeeded(softLineBreak, null); + if (stripNewlines()) { + textContent.whitespace(); + } else { + textContent.line(); + } } @Override @@ -216,7 +216,7 @@ protected void visitChildren(Node parent) { } private void writeText(String text) { - if (context.stripNewlines()) { + if (stripNewlines()) { textContent.writeStripped(text); } else { textContent.write(text); @@ -255,26 +255,15 @@ private void writeLink(Node node, String title, String destination) { } } - private void writeEndOfLineIfNeeded(Node node, Character c) { - if (context.stripNewlines()) { - if (c != null) { - textContent.write(c); - } - if (node.getNext() != null) { - textContent.whitespace(); - } - } else { - if (node.getNext() != null) { - textContent.line(); - } - } + private boolean stripNewlines() { + return context.lineBreakRendering() == LineBreakRendering.STRIP; } - private void writeEndOfLine() { - if (context.stripNewlines()) { - textContent.whitespace(); + private static String stripTrailingNewline(String s) { + if (s.endsWith("\n")) { + return s.substring(0, s.length() - 1); } else { - textContent.line(); + return s; } } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/LineBreakRendering.java b/commonmark/src/main/java/org/commonmark/renderer/text/LineBreakRendering.java new file mode 100644 index 000000000..27eeaf0da --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/text/LineBreakRendering.java @@ -0,0 +1,19 @@ +package org.commonmark.renderer.text; + +/** + * Control how line breaks are rendered. + */ +public enum LineBreakRendering { + /** + * Strip all line breaks within blocks and between blocks, resulting in all the text in a single line. + */ + STRIP, + /** + * Use single line breaks between blocks, not a blank line (also render all lists as tight). + */ + COMPACT, + /** + * Separate blocks by a blank line (and respect tight vs loose lists). + */ + SEPARATE_BLOCKS, +} diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java index 1b1cf327c..b3685f67e 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java @@ -4,10 +4,19 @@ public interface TextContentNodeRendererContext { + /** + * TODO + * + * @return + */ + LineBreakRendering lineBreakRendering(); + /** * @return true for stripping new lines and render text as "single line", * false for keeping all line breaks. + * @deprecated Use {@link #lineBreakRendering()} instead */ + @Deprecated boolean stripNewlines(); /** diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java index 9dd5918af..c36f0a271 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java @@ -14,12 +14,12 @@ */ public class TextContentRenderer implements Renderer { - private final boolean stripNewlines; + private final LineBreakRendering lineBreakRendering; private final List nodeRendererFactories; private TextContentRenderer(Builder builder) { - this.stripNewlines = builder.stripNewlines; + this.lineBreakRendering = builder.lineBreakRendering; this.nodeRendererFactories = new ArrayList<>(builder.nodeRendererFactories.size() + 1); this.nodeRendererFactories.addAll(builder.nodeRendererFactories); @@ -43,7 +43,7 @@ public static Builder builder() { @Override public void render(Node node, Appendable output) { - RendererContext context = new RendererContext(new TextContentWriter(output)); + RendererContext context = new RendererContext(new TextContentWriter(output, lineBreakRendering)); context.render(node); } @@ -59,8 +59,8 @@ public String render(Node node) { */ public static class Builder { - private boolean stripNewlines = false; private List nodeRendererFactories = new ArrayList<>(); + private LineBreakRendering lineBreakRendering = LineBreakRendering.COMPACT; /** * @return the configured {@link TextContentRenderer} @@ -69,15 +69,28 @@ public TextContentRenderer build() { return new TextContentRenderer(this); } + /** + * TODO + * + * @param lineBreakRendering + * @return + */ + public Builder lineBreakRendering(LineBreakRendering lineBreakRendering) { + this.lineBreakRendering = lineBreakRendering; + return this; + } + /** * Set the value of flag for stripping new lines. * * @param stripNewlines true for stripping new lines and render text as "single line", * false for keeping all line breaks * @return {@code this} + * @deprecated Use {@link #lineBreakRendering(LineBreakRendering)} with {@link LineBreakRendering#STRIP} instead */ + @Deprecated public Builder stripNewlines(boolean stripNewlines) { - this.stripNewlines = stripNewlines; + this.lineBreakRendering = stripNewlines ? LineBreakRendering.STRIP : LineBreakRendering.COMPACT; return this; } @@ -134,9 +147,14 @@ private RendererContext(TextContentWriter textContentWriter) { } } + @Override + public LineBreakRendering lineBreakRendering() { + return lineBreakRendering; + } + @Override public boolean stripNewlines() { - return stripNewlines; + return lineBreakRendering == LineBreakRendering.STRIP; } @Override diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java index 0ea56e621..2b9f35070 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java @@ -1,47 +1,99 @@ package org.commonmark.renderer.text; import java.io.IOException; +import java.util.LinkedList; public class TextContentWriter { private final Appendable buffer; + private final LineBreakRendering lineBreakRendering; + private final LinkedList tight = new LinkedList<>(); + + private String blockSeparator = null; private char lastChar; public TextContentWriter(Appendable out) { - buffer = out; + this(out, LineBreakRendering.COMPACT); + } + + public TextContentWriter(Appendable out, LineBreakRendering lineBreakRendering) { + this.buffer = out; + this.lineBreakRendering = lineBreakRendering; } public void whitespace() { if (lastChar != 0 && lastChar != ' ') { - append(' '); + write(' '); } } public void colon() { if (lastChar != 0 && lastChar != ':') { - append(':'); + write(':'); } } public void line() { - if (lastChar != 0 && lastChar != '\n') { - append('\n'); - } + append('\n'); + } + + public void block() { + blockSeparator = lineBreakRendering == LineBreakRendering.STRIP ? " " : // + lineBreakRendering == LineBreakRendering.COMPACT || isTight() ? "\n" : "\n\n"; + } + + public void resetBlock() { + blockSeparator = null; } public void writeStripped(String s) { - append(s.replaceAll("[\\r\\n\\s]+", " ")); + write(s.replaceAll("[\\r\\n\\s]+", " ")); } public void write(String s) { + flushBlockSeparator(); append(s); } public void write(char c) { + flushBlockSeparator(); append(c); } + /** + * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight + * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines + * within the list. + *

    + * Note that changing this does not affect block separators that have already been enqueued with {@link #block()}, + * only future ones. + */ + public void pushTight(boolean tight) { + this.tight.addLast(tight); + } + + /** + * Remove the last "tight" setting from the top of the stack. + */ + public void popTight() { + this.tight.removeLast(); + } + + private boolean isTight() { + return !tight.isEmpty() && tight.getLast(); + } + + /** + * If a block separator has been enqueued with {@link #block()} but not yet written, write it now. + */ + private void flushBlockSeparator() { + if (blockSeparator != null) { + append(blockSeparator); + blockSeparator = null; + } + } + private void append(String s) { try { buffer.append(s); diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index 2f5e61ff8..4fb404644 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -3,6 +3,7 @@ import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.node.Node; import org.commonmark.parser.Parser; +import org.commonmark.testutil.Asserts; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -249,8 +250,6 @@ public void textContentBrakes() { assertEquals("foo bar", rendered); } - - @Test public void textContentHtml() { String rendered; From 03d2843d62c4d68c7315a0c6c46ba868e5208a54 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 23 Sep 2024 22:52:32 +1000 Subject: [PATCH 163/345] Make test case more compact --- .../test/TextContentRendererTest.java | 307 ++++++------------ 1 file changed, 100 insertions(+), 207 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index 4fb404644..fd0c43424 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -12,248 +12,133 @@ public class TextContentRendererTest { @Test public void textContentText() { - String source; - String rendered; + String s; + + s = "foo bar"; + assertCompact(s, "foo bar"); + assertStripped(s, "foo bar"); - source = "foo bar"; - rendered = defaultRenderer(source); - assertEquals("foo bar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo bar", rendered); - - source = "foo foo\n\nbar\nbar"; - rendered = defaultRenderer(source); - assertEquals("foo foo\nbar\nbar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo foo bar bar", rendered); + s = "foo foo\n\nbar\nbar"; + assertCompact(s, "foo foo\nbar\nbar"); + assertStripped(s, "foo foo bar bar"); } @Test public void textContentEmphasis() { - String source; + String s; String rendered; - source = "***foo***"; - rendered = defaultRenderer(source, defaultRenderer()); - assertEquals("foo", rendered); - rendered = strippedRenderer(source); - assertEquals("foo", rendered); - - source = "foo ***foo*** bar ***bar***"; - rendered = defaultRenderer(source); - assertEquals("foo foo bar bar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo foo bar bar", rendered); - - source = "foo\n***foo***\nbar\n\n***bar***"; - rendered = defaultRenderer(source); - assertEquals("foo\nfoo\nbar\nbar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo foo bar bar", rendered); - } + s = "***foo***"; + assertCompact(s, "foo"); + assertStripped(s, "foo"); - private String defaultRenderer(String source, TextContentRenderer textContentRenderer) { - String rendered; - rendered = textContentRenderer.render(parse(source)); - return rendered; + s = "foo ***foo*** bar ***bar***"; + assertCompact(s, "foo foo bar bar"); + assertStripped(s, "foo foo bar bar"); + + s = "foo\n***foo***\nbar\n\n***bar***"; + assertCompact(s, "foo\nfoo\nbar\nbar"); + assertStripped(s, "foo foo bar bar"); } @Test public void textContentQuotes() { - String source; - String rendered; + String s; - source = "foo\n>foo\nbar\n\nbar"; - rendered = defaultRenderer(source); - assertEquals("foo\n«foo\nbar»\nbar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo «foo bar» bar", rendered); + s = "foo\n>foo\nbar\n\nbar"; + assertCompact(s, "foo\n«foo\nbar»\nbar"); + assertStripped(s, "foo «foo bar» bar"); } @Test public void textContentLinks() { - String source; - String expected; - String rendered; - - source = "foo [text](http://link \"title\") bar"; - expected = "foo \"text\" (title: http://link) bar"; - rendered = defaultRenderer(source); - assertEquals(expected, rendered); - rendered = strippedRenderer(source); - assertEquals(expected, rendered); - - source = "foo [text](http://link \"http://link\") bar"; - expected = "foo \"text\" (http://link) bar"; - rendered = defaultRenderer(source); - assertEquals(expected, rendered); - rendered = strippedRenderer(source); - assertEquals(expected, rendered); - - source = "foo [text](http://link) bar"; - expected = "foo \"text\" (http://link) bar"; - rendered = defaultRenderer(source); - assertEquals(expected, rendered); - rendered = strippedRenderer(source); - assertEquals(expected, rendered); - - source = "foo [text]() bar"; - expected = "foo \"text\" bar"; - rendered = defaultRenderer(source); - assertEquals(expected, rendered); - rendered = strippedRenderer(source); - assertEquals(expected, rendered); - - source = "foo http://link bar"; - expected = "foo http://link bar"; - rendered = defaultRenderer(source); - assertEquals(expected, rendered); - rendered = strippedRenderer(source); - assertEquals(expected, rendered); + assertAll("foo [text](http://link \"title\") bar", "foo \"text\" (title: http://link) bar"); + assertAll("foo [text](http://link \"http://link\") bar", "foo \"text\" (http://link) bar"); + assertAll("foo [text](http://link) bar", "foo \"text\" (http://link) bar"); + assertAll("foo [text]() bar", "foo \"text\" bar"); + assertAll("foo http://link bar", "foo http://link bar"); } @Test public void textContentImages() { - String source; - String expected; - String rendered; - - source = "foo ![text](http://link \"title\") bar"; - expected = "foo \"text\" (title: http://link) bar"; - rendered = defaultRenderer(source); - assertEquals(expected, rendered); - rendered = strippedRenderer(source); - assertEquals(expected, rendered); - - source = "foo ![text](http://link) bar"; - expected = "foo \"text\" (http://link) bar"; - rendered = defaultRenderer(source); - assertEquals(expected, rendered); - rendered = strippedRenderer(source); - assertEquals(expected, rendered); - - source = "foo ![text]() bar"; - expected = "foo \"text\" bar"; - rendered = defaultRenderer(source); - assertEquals(expected, rendered); - rendered = strippedRenderer(source); - assertEquals(expected, rendered); + assertAll("foo ![text](http://link \"title\") bar", "foo \"text\" (title: http://link) bar"); + assertAll("foo ![text](http://link) bar", "foo \"text\" (http://link) bar"); + assertAll("foo ![text]() bar", "foo \"text\" bar"); } @Test public void textContentLists() { - String source; - String rendered; + String s; + + s = "foo\n* foo\n* bar\n\nbar"; + assertCompact(s, "foo\n* foo\n* bar\nbar"); + assertStripped(s, "foo foo bar bar"); + + s = "foo\n- foo\n- bar\n\nbar"; + assertCompact(s, "foo\n- foo\n- bar\nbar"); + assertStripped(s, "foo foo bar bar"); + + s = "foo\n1. foo\n2. bar\n\nbar"; + assertCompact(s, "foo\n1. foo\n2. bar\nbar"); + assertStripped(s, "foo 1. foo 2. bar bar"); - source = "foo\n* foo\n* bar\n\nbar"; - rendered = defaultRenderer(source); - assertEquals("foo\n* foo\n* bar\nbar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo foo bar bar", rendered); - - source = "foo\n- foo\n- bar\n\nbar"; - rendered = defaultRenderer(source); - assertEquals("foo\n- foo\n- bar\nbar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo foo bar bar", rendered); - - source = "foo\n1. foo\n2. bar\n\nbar"; - rendered = defaultRenderer(source); - assertEquals("foo\n1. foo\n2. bar\nbar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo 1. foo 2. bar bar", rendered); - - source = "foo\n0) foo\n1) bar\n\nbar"; - rendered = defaultRenderer(source); - assertEquals("foo\n0) foo\n1) bar\nbar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo 0) foo 1) bar bar", rendered); - - source = "bar\n1. foo\n 1. bar\n2. foo"; - rendered = defaultRenderer(source); - assertEquals("bar\n1. foo\n 1. bar\n2. foo", rendered); - rendered = strippedRenderer(source); - assertEquals("bar 1. foo 1. bar 2. foo", rendered); - - source = "bar\n* foo\n - bar\n* foo"; - rendered = defaultRenderer(source); - assertEquals("bar\n* foo\n - bar\n* foo", rendered); - rendered = strippedRenderer(source); - assertEquals("bar foo bar foo", rendered); - - source = "bar\n* foo\n 1. bar\n 2. bar\n* foo"; - rendered = defaultRenderer(source); - assertEquals("bar\n* foo\n 1. bar\n 2. bar\n* foo", rendered); - rendered = strippedRenderer(source); - assertEquals("bar foo 1. bar 2. bar foo", rendered); - - source = "bar\n1. foo\n * bar\n * bar\n2. foo"; - rendered = defaultRenderer(source); - assertEquals("bar\n1. foo\n * bar\n * bar\n2. foo", rendered); - rendered = strippedRenderer(source); - assertEquals("bar 1. foo bar bar 2. foo", rendered); + s = "foo\n0) foo\n1) bar\n\nbar"; + assertCompact(s, "foo\n0) foo\n1) bar\nbar"); + assertStripped(s, "foo 0) foo 1) bar bar"); + + s = "bar\n1. foo\n 1. bar\n2. foo"; + assertCompact(s, "bar\n1. foo\n 1. bar\n2. foo"); + assertStripped(s, "bar 1. foo 1. bar 2. foo"); + + s = "bar\n* foo\n - bar\n* foo"; + assertCompact(s, "bar\n* foo\n - bar\n* foo"); + assertStripped(s, "bar foo bar foo"); + + s = "bar\n* foo\n 1. bar\n 2. bar\n* foo"; + assertCompact(s, "bar\n* foo\n 1. bar\n 2. bar\n* foo"); + assertStripped(s, "bar foo 1. bar 2. bar foo"); + + s = "bar\n1. foo\n * bar\n * bar\n2. foo"; + assertCompact(s, "bar\n1. foo\n * bar\n * bar\n2. foo"); + assertStripped(s, "bar 1. foo bar bar 2. foo"); } @Test public void textContentCode() { - String source; - String expected; - String rendered; - - source = "foo `code` bar"; - expected = "foo \"code\" bar"; - rendered = defaultRenderer(source); - assertEquals(expected, rendered); - rendered = strippedRenderer(source); - assertEquals(expected, rendered); + assertAll("foo `code` bar", "foo \"code\" bar"); } @Test public void textContentCodeBlock() { - String source; - String rendered; + String s; + s = "foo\n```\nfoo\nbar\n```\nbar"; + assertCompact(s, "foo\nfoo\nbar\nbar"); + assertStripped(s, "foo foo bar bar"); - source = "foo\n```\nfoo\nbar\n```\nbar"; - rendered = defaultRenderer(source); - assertEquals("foo\nfoo\nbar\nbar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo foo bar bar", rendered); - - source = "foo\n\n foo\n bar\nbar"; - rendered = defaultRenderer(source); - assertEquals("foo\nfoo\n bar\nbar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo foo bar bar", rendered); + s = "foo\n\n foo\n bar\nbar"; + assertCompact(s, "foo\nfoo\n bar\nbar"); + assertStripped(s, "foo foo bar bar"); } @Test - public void textContentBrakes() { - String source; - String rendered; + public void textContentBreaks() { + String s; + + s = "foo\nbar"; + assertCompact(s, "foo\nbar"); + assertStripped(s, "foo bar"); + + s = "foo \nbar"; + assertCompact(s, "foo\nbar"); + assertStripped(s, "foo bar"); - source = "foo\nbar"; - rendered = defaultRenderer(source); - assertEquals("foo\nbar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo bar", rendered); - - source = "foo \nbar"; - rendered = defaultRenderer(source); - assertEquals("foo\nbar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo bar", rendered); - - source = "foo\n___\nbar"; - rendered = defaultRenderer(source); - assertEquals("foo\n***\nbar", rendered); - rendered = strippedRenderer(source); - assertEquals("foo bar", rendered); + s = "foo\n___\nbar"; + assertCompact(s, "foo\n***\nbar"); + assertStripped(s, "foo bar"); } @Test public void textContentHtml() { - String rendered; - String html = "\n" + " \n" + " \n" + " \n" + "
    \n" + @@ -261,12 +146,10 @@ public void textContentHtml() { "
    "; - rendered = defaultRenderer(html); - assertEquals(html, rendered); + assertCompact(html, html); html = "foo foobar bar"; - rendered = defaultRenderer(html); - assertEquals(html, rendered); + assertCompact(html, html); } private TextContentRenderer defaultRenderer() { @@ -280,12 +163,22 @@ private TextContentRenderer strippedRenderer() { private Node parse(String source) { return Parser.builder().build().parse(source); } + + private void assertCompact(String source, String expected) { + var doc = parse(source); + var actualRendering = defaultRenderer().render(doc); + Asserts.assertRendering(source, expected, actualRendering); + } - private String strippedRenderer(String source) { - return strippedRenderer().render(parse(source)); + private void assertStripped(String source, String expected) { + var doc = parse(source); + var actualRendering = strippedRenderer().render(doc); + Asserts.assertRendering(source, expected, actualRendering); } - private String defaultRenderer(String source) { - return defaultRenderer().render(parse(source)); + private void assertAll(String source, String expected) { + assertCompact(source, expected); + assertStripped(source, expected); + // TODO } } From 97eeb6a27e8e043908b2d221d8e3e55ffe76e660 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 23 Sep 2024 23:04:21 +1000 Subject: [PATCH 164/345] Tests for separate blocks, implement tight/loose --- .../text/CoreTextContentNodeRenderer.java | 6 ++- .../test/TextContentRendererTest.java | 47 +++++++++++++++++-- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java index 64968ae76..68b1fbce5 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -74,9 +74,10 @@ public void visit(BlockQuote blockQuote) { @Override public void visit(BulletList bulletList) { - // TODO: isTight() + textContent.pushTight(bulletList.isTight()); listHolder = new BulletListHolder(listHolder, bulletList); visitChildren(bulletList); + textContent.popTight(); textContent.block(); listHolder = listHolder.getParent(); } @@ -178,9 +179,10 @@ public void visit(ListItem listItem) { @Override public void visit(OrderedList orderedList) { - // TODO: isTight() + textContent.pushTight(orderedList.isTight()); listHolder = new OrderedListHolder(listHolder, orderedList); visitChildren(orderedList); + textContent.popTight(); textContent.block(); listHolder = listHolder.getParent(); } diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index fd0c43424..e459c63a3 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -1,5 +1,6 @@ package org.commonmark.test; +import org.commonmark.renderer.text.LineBreakRendering; import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.node.Node; import org.commonmark.parser.Parser; @@ -20,6 +21,7 @@ public void textContentText() { s = "foo foo\n\nbar\nbar"; assertCompact(s, "foo foo\nbar\nbar"); + assertSeparate(s, "foo foo\n\nbar\nbar"); assertStripped(s, "foo foo bar bar"); } @@ -38,6 +40,7 @@ public void textContentEmphasis() { s = "foo\n***foo***\nbar\n\n***bar***"; assertCompact(s, "foo\nfoo\nbar\nbar"); + assertSeparate(s, "foo\nfoo\nbar\n\nbar"); assertStripped(s, "foo foo bar bar"); } @@ -47,6 +50,7 @@ public void textContentQuotes() { s = "foo\n>foo\nbar\n\nbar"; assertCompact(s, "foo\n«foo\nbar»\nbar"); + assertSeparate(s, "foo\n\n«foo\nbar»\n\nbar"); assertStripped(s, "foo «foo bar» bar"); } @@ -72,35 +76,52 @@ public void textContentLists() { s = "foo\n* foo\n* bar\n\nbar"; assertCompact(s, "foo\n* foo\n* bar\nbar"); + assertSeparate(s, "foo\n\n* foo\n* bar\n\nbar"); assertStripped(s, "foo foo bar bar"); s = "foo\n- foo\n- bar\n\nbar"; assertCompact(s, "foo\n- foo\n- bar\nbar"); + assertSeparate(s, "foo\n\n- foo\n- bar\n\nbar"); assertStripped(s, "foo foo bar bar"); s = "foo\n1. foo\n2. bar\n\nbar"; assertCompact(s, "foo\n1. foo\n2. bar\nbar"); + assertSeparate(s, "foo\n\n1. foo\n2. bar\n\nbar"); assertStripped(s, "foo 1. foo 2. bar bar"); s = "foo\n0) foo\n1) bar\n\nbar"; assertCompact(s, "foo\n0) foo\n1) bar\nbar"); + assertSeparate(s, "foo\n0) foo\n\n1) bar\n\nbar"); assertStripped(s, "foo 0) foo 1) bar bar"); s = "bar\n1. foo\n 1. bar\n2. foo"; assertCompact(s, "bar\n1. foo\n 1. bar\n2. foo"); + assertSeparate(s, "bar\n\n1. foo\n 1. bar\n2. foo"); assertStripped(s, "bar 1. foo 1. bar 2. foo"); s = "bar\n* foo\n - bar\n* foo"; assertCompact(s, "bar\n* foo\n - bar\n* foo"); + assertSeparate(s, "bar\n\n* foo\n - bar\n* foo"); assertStripped(s, "bar foo bar foo"); s = "bar\n* foo\n 1. bar\n 2. bar\n* foo"; assertCompact(s, "bar\n* foo\n 1. bar\n 2. bar\n* foo"); + assertSeparate(s, "bar\n\n* foo\n 1. bar\n 2. bar\n* foo"); assertStripped(s, "bar foo 1. bar 2. bar foo"); s = "bar\n1. foo\n * bar\n * bar\n2. foo"; assertCompact(s, "bar\n1. foo\n * bar\n * bar\n2. foo"); + assertSeparate(s, "bar\n\n1. foo\n * bar\n * bar\n2. foo"); assertStripped(s, "bar 1. foo bar bar 2. foo"); + + // For a loose list (not tight) + s = "foo\n\n* bar\n\n* baz"; + // Compact ignores loose + assertCompact(s, "foo\n* bar\n* baz"); + // Separate preserves it + assertSeparate(s, "foo\n\n* bar\n\n* baz"); + assertStripped(s, "foo bar baz"); + } @Test @@ -113,10 +134,12 @@ public void textContentCodeBlock() { String s; s = "foo\n```\nfoo\nbar\n```\nbar"; assertCompact(s, "foo\nfoo\nbar\nbar"); + assertSeparate(s, "foo\n\nfoo\nbar\n\nbar"); assertStripped(s, "foo foo bar bar"); s = "foo\n\n foo\n bar\nbar"; assertCompact(s, "foo\nfoo\n bar\nbar"); + assertSeparate(s, "foo\n\nfoo\n bar\n\nbar"); assertStripped(s, "foo foo bar bar"); } @@ -126,14 +149,17 @@ public void textContentBreaks() { s = "foo\nbar"; assertCompact(s, "foo\nbar"); + assertSeparate(s, "foo\nbar"); assertStripped(s, "foo bar"); s = "foo \nbar"; assertCompact(s, "foo\nbar"); + assertSeparate(s, "foo\nbar"); assertStripped(s, "foo bar"); s = "foo\n___\nbar"; assertCompact(s, "foo\n***\nbar"); + assertSeparate(s, "foo\n\n***\n\nbar"); assertStripped(s, "foo bar"); } @@ -147,15 +173,20 @@ public void textContentHtml() { " \n" + ""; assertCompact(html, html); + assertSeparate(html, html); html = "foo foobar bar"; - assertCompact(html, html); + assertAll(html, html); } - private TextContentRenderer defaultRenderer() { + private TextContentRenderer compactRenderer() { return TextContentRenderer.builder().build(); } + private TextContentRenderer separateBlocksRenderer() { + return TextContentRenderer.builder().lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS).build(); + } + private TextContentRenderer strippedRenderer() { return TextContentRenderer.builder().stripNewlines(true).build(); } @@ -163,10 +194,16 @@ private TextContentRenderer strippedRenderer() { private Node parse(String source) { return Parser.builder().build().parse(source); } - + private void assertCompact(String source, String expected) { var doc = parse(source); - var actualRendering = defaultRenderer().render(doc); + var actualRendering = compactRenderer().render(doc); + Asserts.assertRendering(source, expected, actualRendering); + } + + private void assertSeparate(String source, String expected) { + var doc = parse(source); + var actualRendering = separateBlocksRenderer().render(doc); Asserts.assertRendering(source, expected, actualRendering); } @@ -178,7 +215,7 @@ private void assertStripped(String source, String expected) { private void assertAll(String source, String expected) { assertCompact(source, expected); + assertSeparate(source, expected); assertStripped(source, expected); - // TODO } } From 1906ee9c6966616ed65f1d4eba8e925c9cf3a1f1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 24 Sep 2024 18:52:23 +1000 Subject: [PATCH 165/345] Document lineBreakRendering --- .../renderer/text/TextContentNodeRendererContext.java | 4 +--- .../org/commonmark/renderer/text/TextContentRenderer.java | 7 ++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java index b3685f67e..d6fcb8d77 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java @@ -5,9 +5,7 @@ public interface TextContentNodeRendererContext { /** - * TODO - * - * @return + * Controls how line breaks should be rendered, see {@link LineBreakRendering}. */ LineBreakRendering lineBreakRendering(); diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java index c36f0a271..57006bfc5 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java @@ -70,10 +70,11 @@ public TextContentRenderer build() { } /** - * TODO + * Configure how line breaks (newlines) are rendered, see {@link LineBreakRendering}. + * The default is {@link LineBreakRendering#COMPACT}. * - * @param lineBreakRendering - * @return + * @param lineBreakRendering the mode to use + * @return {@code this} */ public Builder lineBreakRendering(LineBreakRendering lineBreakRendering) { this.lineBreakRendering = lineBreakRendering; From 6e529d4117a5780b9eaf7377f96fb42880a93309 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 24 Sep 2024 21:44:21 +1000 Subject: [PATCH 166/345] Fix tables rendering --- .../TableTextContentNodeRenderer.java | 30 ++---- .../ext/gfm/tables/TablesTextContentTest.java | 102 +++++++++++------- .../test/TextContentRendererTest.java | 35 +++--- 3 files changed, 88 insertions(+), 79 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java index 94b0e8665..7d28f61a8 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java @@ -23,10 +23,11 @@ public TableTextContentNodeRenderer(TextContentNodeRendererContext context) { } protected void renderBlock(TableBlock tableBlock) { + // Render rows tight + textContentWriter.pushTight(true); renderChildren(tableBlock); - if (tableBlock.getNext() != null) { - textContentWriter.write("\n"); - } + textContentWriter.popTight(); + textContentWriter.block(); } protected void renderHead(TableHead tableHead) { @@ -38,33 +39,24 @@ protected void renderBody(TableBody tableBody) { } protected void renderRow(TableRow tableRow) { - textContentWriter.line(); renderChildren(tableRow); - textContentWriter.line(); + textContentWriter.block(); } protected void renderCell(TableCell tableCell) { renderChildren(tableCell); - textContentWriter.write('|'); - textContentWriter.whitespace(); - } - - private void renderLastCell(TableCell tableCell) { - renderChildren(tableCell); + // For the last cell in row, don't render the delimiter + if (tableCell.getNext() != null) { + textContentWriter.write('|'); + textContentWriter.whitespace(); + } } private void renderChildren(Node parent) { Node node = parent.getFirstChild(); while (node != null) { Node next = node.getNext(); - - // For last cell in row, we dont render the delimiter. - if (node instanceof TableCell && next == null) { - renderLastCell((TableCell) node); - } else { - context.render(node); - } - + context.render(node); node = next; } } diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java index 7d6feb248..c5ef8cb5a 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java @@ -2,137 +2,165 @@ import org.commonmark.Extension; import org.commonmark.parser.Parser; +import org.commonmark.renderer.text.LineBreakRendering; import org.commonmark.renderer.text.TextContentRenderer; -import org.commonmark.testutil.RenderingTestCase; +import org.commonmark.testutil.Asserts; import org.junit.Test; import java.util.Set; -public class TablesTextContentTest extends RenderingTestCase { +public class TablesTextContentTest { private static final Set EXTENSIONS = Set.of(TablesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final TextContentRenderer RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS).build(); + private static final TextContentRenderer COMPACT_RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS).build(); + private static final TextContentRenderer SEPARATE_RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS) + .lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS).build(); + private static final TextContentRenderer STRIPPED_RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS) + .lineBreakRendering(LineBreakRendering.STRIP).build(); + @Test public void oneHeadNoBody() { - assertRendering("Abc|Def\n---|---", "Abc| Def\n"); + assertCompact("Abc|Def\n---|---", "Abc| Def"); } @Test public void oneColumnOneHeadNoBody() { - String expected = "Abc\n"; - assertRendering("|Abc\n|---\n", expected); - assertRendering("|Abc|\n|---|\n", expected); - assertRendering("Abc|\n---|\n", expected); + String expected = "Abc"; + assertCompact("|Abc\n|---\n", expected); + assertCompact("|Abc|\n|---|\n", expected); + assertCompact("Abc|\n---|\n", expected); // Pipe required on separator - assertRendering("|Abc\n---\n", "|Abc"); + assertCompact("|Abc\n---\n", "|Abc"); // Pipe required on head - assertRendering("Abc\n|---\n", "Abc\n|---"); + assertCompact("Abc\n|---\n", "Abc\n|---"); } @Test public void oneColumnOneHeadOneBody() { - String expected = "Abc\n1\n"; - assertRendering("|Abc\n|---\n|1", expected); - assertRendering("|Abc|\n|---|\n|1|", expected); - assertRendering("Abc|\n---|\n1|", expected); + String expected = "Abc\n1"; + assertCompact("|Abc\n|---\n|1", expected); + assertCompact("|Abc|\n|---|\n|1|", expected); + assertCompact("Abc|\n---|\n1|", expected); // Pipe required on separator - assertRendering("|Abc\n---\n|1", "|Abc\n|1"); + assertCompact("|Abc\n---\n|1", "|Abc\n|1"); } @Test public void oneHeadOneBody() { - assertRendering("Abc|Def\n---|---\n1|2", "Abc| Def\n1| 2\n"); + assertCompact("Abc|Def\n---|---\n1|2", "Abc| Def\n1| 2"); } @Test public void separatorMustNotHaveLessPartsThanHead() { - assertRendering("Abc|Def|Ghi\n---|---\n1|2|3", "Abc|Def|Ghi\n---|---\n1|2|3"); + assertCompact("Abc|Def|Ghi\n---|---\n1|2|3", "Abc|Def|Ghi\n---|---\n1|2|3"); } @Test public void padding() { - assertRendering(" Abc | Def \n --- | --- \n 1 | 2 ", "Abc| Def\n1| 2\n"); + assertCompact(" Abc | Def \n --- | --- \n 1 | 2 ", "Abc| Def\n1| 2"); } @Test public void paddingWithCodeBlockIndentation() { - assertRendering("Abc|Def\n---|---\n 1|2", "Abc| Def\n1| 2\n"); + assertCompact("Abc|Def\n---|---\n 1|2", "Abc| Def\n1| 2"); } @Test public void pipesOnOutside() { - assertRendering("|Abc|Def|\n|---|---|\n|1|2|", "Abc| Def\n1| 2\n"); + assertCompact("|Abc|Def|\n|---|---|\n|1|2|", "Abc| Def\n1| 2"); } @Test public void inlineElements() { - assertRendering("*Abc*|Def\n---|---\n1|2", "Abc| Def\n1| 2\n"); + assertCompact("*Abc*|Def\n---|---\n1|2", "Abc| Def\n1| 2"); } @Test public void escapedPipe() { - assertRendering("Abc|Def\n---|---\n1\\|2|20", "Abc| Def\n1|2| 20\n"); + assertCompact("Abc|Def\n---|---\n1\\|2|20", "Abc| Def\n1|2| 20"); } @Test public void alignLeft() { - assertRendering("Abc|Def\n:---|---\n1|2", "Abc| Def\n1| 2\n"); + assertCompact("Abc|Def\n:---|---\n1|2", "Abc| Def\n1| 2"); } @Test public void alignRight() { - assertRendering("Abc|Def\n---:|---\n1|2", "Abc| Def\n1| 2\n"); + assertCompact("Abc|Def\n---:|---\n1|2", "Abc| Def\n1| 2"); } @Test public void alignCenter() { - assertRendering("Abc|Def\n:---:|---\n1|2", "Abc| Def\n1| 2\n"); + assertCompact("Abc|Def\n:---:|---\n1|2", "Abc| Def\n1| 2"); } @Test public void alignCenterSecond() { - assertRendering("Abc|Def\n---|:---:\n1|2", "Abc| Def\n1| 2\n"); + assertCompact("Abc|Def\n---|:---:\n1|2", "Abc| Def\n1| 2"); } @Test public void alignLeftWithSpaces() { - assertRendering("Abc|Def\n :--- |---\n1|2", "Abc| Def\n1| 2\n"); + assertCompact("Abc|Def\n :--- |---\n1|2", "Abc| Def\n1| 2"); } @Test public void alignmentMarkerMustBeNextToDashes() { - assertRendering("Abc|Def\n: ---|---", "Abc|Def\n: ---|---"); - assertRendering("Abc|Def\n--- :|---", "Abc|Def\n--- :|---"); - assertRendering("Abc|Def\n---|: ---", "Abc|Def\n---|: ---"); - assertRendering("Abc|Def\n---|--- :", "Abc|Def\n---|--- :"); + assertCompact("Abc|Def\n: ---|---", "Abc|Def\n: ---|---"); + assertCompact("Abc|Def\n--- :|---", "Abc|Def\n--- :|---"); + assertCompact("Abc|Def\n---|: ---", "Abc|Def\n---|: ---"); + assertCompact("Abc|Def\n---|--- :", "Abc|Def\n---|--- :"); } @Test public void bodyCanNotHaveMoreColumnsThanHead() { - assertRendering("Abc|Def\n---|---\n1|2|3", "Abc| Def\n1| 2\n"); + assertCompact("Abc|Def\n---|---\n1|2|3", "Abc| Def\n1| 2"); } @Test public void bodyWithFewerColumnsThanHeadResultsInEmptyCells() { - assertRendering("Abc|Def|Ghi\n---|---|---\n1|2", "Abc| Def| Ghi\n1| 2| \n"); + assertCompact("Abc|Def|Ghi\n---|---|---\n1|2", "Abc| Def| Ghi\n1| 2| "); } @Test public void insideBlockQuote() { - assertRendering("> Abc|Def\n> ---|---\n> 1|2", "«\nAbc| Def\n1| 2\n»"); + assertCompact("> Abc|Def\n> ---|---\n> 1|2", "«Abc| Def\n1| 2»"); } @Test public void tableWithLazyContinuationLine() { - assertRendering("Abc|Def\n---|---\n1|2\nlazy", "Abc| Def\n1| 2\nlazy| \n"); + assertCompact("Abc|Def\n---|---\n1|2\nlazy", "Abc| Def\n1| 2\nlazy| "); + } + + @Test + public void tableBetweenOtherBlocks() { + var s = "Foo\n\nAbc|Def\n---|---\n1|2\n\nBar"; + assertCompact(s, "Foo\nAbc| Def\n1| 2\nBar"); + assertSeparate(s, "Foo\n\nAbc| Def\n1| 2\n\nBar"); + assertStripped(s, "Foo Abc| Def 1| 2 Bar"); + } + + private void assertCompact(String source, String expected) { + var doc = PARSER.parse(source); + var actualRendering = COMPACT_RENDERER.render(doc); + Asserts.assertRendering(source, expected, actualRendering); + } + + private void assertSeparate(String source, String expected) { + var doc = PARSER.parse(source); + var actualRendering = SEPARATE_RENDERER.render(doc); + Asserts.assertRendering(source, expected, actualRendering); } - @Override - protected String render(String source) { - return RENDERER.render(PARSER.parse(source)); + private void assertStripped(String source, String expected) { + var doc = PARSER.parse(source); + var actualRendering = STRIPPED_RENDERER.render(doc); + Asserts.assertRendering(source, expected, actualRendering); } } diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index e459c63a3..1c07f466d 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -2,7 +2,6 @@ import org.commonmark.renderer.text.LineBreakRendering; import org.commonmark.renderer.text.TextContentRenderer; -import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.testutil.Asserts; import org.junit.Test; @@ -11,6 +10,12 @@ public class TextContentRendererTest { + private static final Parser PARSER = Parser.builder().build(); + private static final TextContentRenderer COMPACT_RENDERER = TextContentRenderer.builder().build(); + private static final TextContentRenderer SEPARATE_RENDERER = TextContentRenderer.builder() + .lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS).build(); + private static final TextContentRenderer STRIPPED_RENDERER = TextContentRenderer.builder().stripNewlines(true).build(); + @Test public void textContentText() { String s; @@ -179,37 +184,21 @@ public void textContentHtml() { assertAll(html, html); } - private TextContentRenderer compactRenderer() { - return TextContentRenderer.builder().build(); - } - - private TextContentRenderer separateBlocksRenderer() { - return TextContentRenderer.builder().lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS).build(); - } - - private TextContentRenderer strippedRenderer() { - return TextContentRenderer.builder().stripNewlines(true).build(); - } - - private Node parse(String source) { - return Parser.builder().build().parse(source); - } - private void assertCompact(String source, String expected) { - var doc = parse(source); - var actualRendering = compactRenderer().render(doc); + var doc = PARSER.parse(source); + var actualRendering = COMPACT_RENDERER.render(doc); Asserts.assertRendering(source, expected, actualRendering); } private void assertSeparate(String source, String expected) { - var doc = parse(source); - var actualRendering = separateBlocksRenderer().render(doc); + var doc = PARSER.parse(source); + var actualRendering = SEPARATE_RENDERER.render(doc); Asserts.assertRendering(source, expected, actualRendering); } private void assertStripped(String source, String expected) { - var doc = parse(source); - var actualRendering = strippedRenderer().render(doc); + var doc = PARSER.parse(source); + var actualRendering = STRIPPED_RENDERER.render(doc); Asserts.assertRendering(source, expected, actualRendering); } From 6cef998677653dd4b4c42fe8c988749cba5c48ab Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 24 Sep 2024 21:51:42 +1000 Subject: [PATCH 167/345] Add test for heading --- .../java/org/commonmark/test/TextContentRendererTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index 1c07f466d..e32aa9d84 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -30,6 +30,13 @@ public void textContentText() { assertStripped(s, "foo foo bar bar"); } + @Test + public void textContentHeading() { + assertCompact("# Heading\n\nFoo", "Heading\nFoo"); + assertSeparate("# Heading\n\nFoo", "Heading\n\nFoo"); + assertStripped("# Heading\n\nFoo", "Heading: Foo"); + } + @Test public void textContentEmphasis() { String s; From cebb01052456c13eb187dd3722247e35af102b0a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 6 Oct 2024 12:00:30 +1100 Subject: [PATCH 168/345] TextContentRenderer: Fix overriding of core node rendering --- .../renderer/text/TextContentRenderer.java | 8 ++-- .../test/TextContentRendererTest.java | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java index 57006bfc5..d64d0c7ef 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java @@ -140,11 +140,9 @@ private class RendererContext implements TextContentNodeRendererContext { private RendererContext(TextContentWriter textContentWriter) { this.textContentWriter = textContentWriter; - // The first node renderer for a node type "wins". - for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) { - TextContentNodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i); - NodeRenderer nodeRenderer = nodeRendererFactory.create(this); - nodeRendererMap.add(nodeRenderer); + for (var factory : nodeRendererFactories) { + var renderer = factory.create(this); + nodeRendererMap.add(renderer); } } diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index e32aa9d84..45d7aa91d 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -1,11 +1,18 @@ package org.commonmark.test; +import org.commonmark.node.Link; +import org.commonmark.node.Node; +import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.text.LineBreakRendering; +import org.commonmark.renderer.text.TextContentNodeRendererContext; +import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.parser.Parser; import org.commonmark.testutil.Asserts; import org.junit.Test; +import java.util.Set; + import static org.junit.Assert.assertEquals; public class TextContentRendererTest { @@ -191,6 +198,41 @@ public void textContentHtml() { assertAll(html, html); } + @Test + public void testOverrideNodeRendering() { + var nodeRendererFactory = new TextContentNodeRendererFactory() { + @Override + public NodeRenderer create(TextContentNodeRendererContext context) { + return new NodeRenderer() { + + @Override + public Set> getNodeTypes() { + return Set.of(Link.class); + } + + @Override + public void render(Node node) { + context.getWriter().write('"'); + renderChildren(node); + context.getWriter().write('"'); + } + + private void renderChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } + }; + } + }; + var renderer = TextContentRenderer.builder().nodeRendererFactory(nodeRendererFactory).build(); + var source = "Hi [Example](https://example.com)"; + Asserts.assertRendering(source, "Hi \"Example\"", renderer.render(PARSER.parse(source))); + } + private void assertCompact(String source, String expected) { var doc = PARSER.parse(source); var actualRendering = COMPACT_RENDERER.render(doc); From 6e93f850d761490b09366761db21ffd4cbbe8f70 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 12 Oct 2024 22:35:30 +1100 Subject: [PATCH 169/345] Add LineReader for reading lines with terminators --- .../commonmark/internal/util/LineReader.java | 149 ++++++++++++++++++ .../internal/util/LineReaderTest.java | 128 +++++++++++++++ 2 files changed, 277 insertions(+) create mode 100644 commonmark/src/main/java/org/commonmark/internal/util/LineReader.java create mode 100644 commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/util/LineReader.java b/commonmark/src/main/java/org/commonmark/internal/util/LineReader.java new file mode 100644 index 000000000..b44098257 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/util/LineReader.java @@ -0,0 +1,149 @@ +package org.commonmark.internal.util; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; + +/** + * Reads lines from a reader like {@link java.io.BufferedReader} but also returns the line terminators. + *

    + * Line terminators can be either a line feed {@code "\n"}, carriage return {@code "\r"}, or a carriage return followed + * by a line feed {@code "\r\n"}. Call {@link #getLineTerminator()} after {@link #readLine()} to obtain the + * corresponding line terminator. If a stream has a line at the end without a terminator, {@link #getLineTerminator()} + * returns {@code null}. + */ +public class LineReader implements Closeable { + + // Same as java.io.BufferedReader + static final int CHAR_BUFFER_SIZE = 8192; + static final int EXPECTED_LINE_LENGTH = 80; + + private Reader reader; + private char[] cbuf; + + private int position = 0; + private int limit = 0; + + private String lineTerminator = null; + + public LineReader(Reader reader) { + this.reader = reader; + this.cbuf = new char[CHAR_BUFFER_SIZE]; + } + + /** + * Read a line of text. + * + * @return the line, or {@code null} when the end of the stream has been reached and no more lines can be read + */ + public String readLine() throws IOException { + StringBuilder sb = null; + boolean cr = false; + + while (true) { + if (position >= limit) { + fill(); + } + + if (cr) { + // We saw a CR before, check if we have CR LF or just CR. + if (position < limit && cbuf[position] == '\n') { + position++; + return line(sb.toString(), "\r\n"); + } else { + return line(sb.toString(), "\r"); + } + } + + if (position >= limit) { + // End of stream, return either the last line without terminator or null for end. + return line(sb != null ? sb.toString() : null, null); + } + + int start = position; + int i = position; + for (; i < limit; i++) { + char c = cbuf[i]; + if (c == '\n') { + position = i + 1; + return line(finish(sb, start, i), "\n"); + } else if (c == '\r') { + if (i + 1 < limit) { + // We know what the next character is, so we can check now whether we have + // a CR LF or just a CR and return. + if (cbuf[i + 1] == '\n') { + position = i + 2; + return line(finish(sb, start, i), "\r\n"); + } else { + position = i + 1; + return line(finish(sb, start, i), "\r"); + } + } else { + // We don't know what the next character is yet, check on next iteration. + cr = true; + position = i + 1; + break; + } + } + } + + if (position < i) { + position = i; + } + + // Haven't found a finished line yet, copy the data from the buffer so that we can fill + // the buffer again. + if (sb == null) { + sb = new StringBuilder(EXPECTED_LINE_LENGTH); + } + sb.append(cbuf, start, i - start); + } + } + + /** + * Return the line terminator of the last read line from {@link #readLine()}. + * + * @return {@code "\n"}, {@code "\r"}, {@code "\r\n"}, or {@code null} + */ + public String getLineTerminator() { + return lineTerminator; + } + + @Override + public void close() throws IOException { + if (reader == null) { + return; + } + try { + reader.close(); + } finally { + reader = null; + cbuf = null; + } + } + + private void fill() throws IOException { + int read; + do { + read = reader.read(cbuf, 0, cbuf.length); + } while (read == 0); + if (read > 0) { + limit = read; + position = 0; + } + } + + private String line(String line, String lineTerminator) { + this.lineTerminator = lineTerminator; + return line; + } + + private String finish(StringBuilder sb, int start, int end) { + int len = end - start; + if (sb == null) { + return new String(cbuf, start, len); + } else { + return sb.append(cbuf, start, len).toString(); + } + } +} diff --git a/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java b/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java new file mode 100644 index 000000000..fc48623a8 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java @@ -0,0 +1,128 @@ +package org.commonmark.internal.util; + +import org.junit.Test; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; + +import static java.util.stream.Collectors.joining; +import static org.commonmark.internal.util.LineReader.CHAR_BUFFER_SIZE; +import static org.junit.Assert.*; + +public class LineReaderTest { + + @Test + public void testReadLine() throws IOException { + assertLines(); + + assertLines("", "\n"); + assertLines("foo", "\n", "bar", "\n"); + assertLines("foo", "\n", "bar", null); + assertLines("", "\n", "", "\n"); + assertLines(repeat("a", CHAR_BUFFER_SIZE - 1), "\n"); + assertLines(repeat("a", CHAR_BUFFER_SIZE), "\n"); + assertLines(repeat("a", CHAR_BUFFER_SIZE) + "b", "\n"); + + assertLines("", "\r\n"); + assertLines("foo", "\r\n", "bar", "\r\n"); + assertLines("foo", "\r\n", "bar", null); + assertLines("", "\r\n", "", "\r\n"); + assertLines(repeat("a", CHAR_BUFFER_SIZE - 2), "\r\n"); + assertLines(repeat("a", CHAR_BUFFER_SIZE - 1), "\r\n"); + assertLines(repeat("a", CHAR_BUFFER_SIZE), "\r\n"); + assertLines(repeat("a", CHAR_BUFFER_SIZE) + "b", "\r\n"); + + assertLines("", "\r"); + assertLines("foo", "\r", "bar", "\r"); + assertLines("foo", "\r", "bar", null); + assertLines("", "\r", "", "\r"); + assertLines(repeat("a", CHAR_BUFFER_SIZE - 1), "\r"); + assertLines(repeat("a", CHAR_BUFFER_SIZE), "\r"); + assertLines(repeat("a", CHAR_BUFFER_SIZE) + "b", "\r"); + + assertLines("", "\n", "", "\r", "", "\r\n", "", "\n"); + assertLines("what", "\r", "are", "\r", "", "\r", "you", "\r\n", "", "\r\n", "even", "\n", "doing", null); + } + + @Test + public void testClose() throws IOException { + var reader = new InputStreamReader(new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8))); + var lineReader = new LineReader(reader); + lineReader.close(); + lineReader.close(); + try { + reader.read(); + fail("Expected read to throw after closing reader"); + } catch (IOException e) { + // Expected + } + } + + private void assertLines(String... s) throws IOException { + assertTrue("Expected parts needs to be even (pairs of content and terminator)", s.length % 2 == 0); + var input = Arrays.stream(s).filter(Objects::nonNull).collect(joining("")); + + assertLines(new StringReader(input), s); + assertLines(new SlowStringReader(input), s); + } + + private static void assertLines(Reader reader, String... expectedParts) throws IOException { + try (var lineReader = new LineReader(reader)) { + var lines = new ArrayList<>(); + String line; + while ((line = lineReader.readLine()) != null) { + lines.add(line); + lines.add(lineReader.getLineTerminator()); + } + assertNull(lineReader.getLineTerminator()); + assertEquals(Arrays.asList(expectedParts), lines); + } + } + + private static String repeat(String s, int count) { + StringBuilder sb = new StringBuilder(s.length() * count); + for (int i = 0; i < count; i++) { + sb.append(s); + } + return sb.toString(); + } + + /** + * Reader that only reads 0 or 1 chars at a time to test the corner cases. + */ + private static class SlowStringReader extends Reader { + + private final String s; + private int position = 0; + private boolean empty = false; + + private SlowStringReader(String s) { + this.s = s; + } + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + Objects.checkFromIndexSize(off, len, cbuf.length); + if (len == 0) { + return 0; + } + empty = !empty; + if (empty) { + // Return 0 every other time to test handling of 0. + return 0; + } + if (position >= s.length()) { + return -1; + } + cbuf[off] = s.charAt(position++); + return 1; + } + + @Override + public void close() throws IOException { + } + } +} From a6b3daa6665355490a0655f84d5118c9db9e33cb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 16 Oct 2024 22:29:01 +1100 Subject: [PATCH 170/345] Also include "input index" in SourceSpan The existing line/column indexes in `SourceSpan` are useful for some cases, e.g. editors that are line based. But for other cases, it's useful to be able to get the index within the original input string. An example: If the input string is "foo\n\nbar", the "bar" paragraph has the following `SourceSpan`: line 2 (third line), column 0, length 3. With this change, now it also includes the input index: 5 ("b" is the character at index 5 in the string). That means it's possible to use e.g. `substring` instead of having to split the input text into lines first. --- .../internal/AutolinkPostProcessor.java | 3 +- .../commonmark/ext/autolink/AutolinkTest.java | 14 +- .../ext/footnotes/FootnotesTest.java | 4 +- .../gfm/strikethrough/StrikethroughTest.java | 2 +- .../commonmark/ext/gfm/tables/TablesTest.java | 40 +-- .../image/attributes/ImageAttributesTest.java | 2 +- .../java/org/commonmark/ext/ins/InsTest.java | 2 +- .../commonmark/internal/DocumentParser.java | 40 +-- .../java/org/commonmark/node/SourceSpan.java | 70 +++- .../java/org/commonmark/node/SourceSpans.java | 4 +- .../org/commonmark/parser/SourceLine.java | 5 +- .../org/commonmark/parser/beta/Scanner.java | 2 +- .../commonmark/parser/beta/ScannerTest.java | 28 +- .../org/commonmark/test/SourceLineTest.java | 20 +- .../commonmark/test/SourceSpanRenderer.java | 70 ++-- .../org/commonmark/test/SourceSpanTest.java | 63 ++++ .../org/commonmark/test/SourceSpansTest.java | 318 ++++++++++-------- 17 files changed, 425 insertions(+), 262 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/test/SourceSpanTest.java diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java index e00692158..ee8847911 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java @@ -61,8 +61,7 @@ private static Text createTextNode(String literal, Span span, SourceSpan sourceS String text = literal.substring(beginIndex, endIndex); Text textNode = new Text(text); if (sourceSpan != null) { - int length = endIndex - beginIndex; - textNode.addSourceSpan(SourceSpan.of(sourceSpan.getLineIndex(), beginIndex, length)); + textNode.addSourceSpan(sourceSpan.subSpan(beginIndex, endIndex)); } return textNode; } diff --git a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java index 6c4c18d0a..67fd69abe 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -71,43 +71,43 @@ public void sourceSpans() { Paragraph paragraph = (Paragraph) document.getFirstChild(); Text abc = (Text) paragraph.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 3)), + assertEquals(List.of(SourceSpan.of(0, 0, 0, 3)), abc.getSourceSpans()); assertTrue(abc.getNext() instanceof SoftLineBreak); Link one = (Link) abc.getNext().getNext(); assertEquals("http://example.com/one", one.getDestination()); - assertEquals(List.of(SourceSpan.of(1, 0, 22)), + assertEquals(List.of(SourceSpan.of(1, 0, 4, 22)), one.getSourceSpans()); assertTrue(one.getNext() instanceof SoftLineBreak); Text def = (Text) one.getNext().getNext(); assertEquals("def ", def.getLiteral()); - assertEquals(List.of(SourceSpan.of(2, 0, 4)), + assertEquals(List.of(SourceSpan.of(2, 0, 27, 4)), def.getSourceSpans()); Link two = (Link) def.getNext(); assertEquals("http://example.com/two", two.getDestination()); - assertEquals(List.of(SourceSpan.of(2, 4, 22)), + assertEquals(List.of(SourceSpan.of(2, 4, 31, 22)), two.getSourceSpans()); assertTrue(two.getNext() instanceof SoftLineBreak); Text ghi = (Text) two.getNext().getNext(); assertEquals("ghi ", ghi.getLiteral()); - assertEquals(List.of(SourceSpan.of(3, 0, 4)), + assertEquals(List.of(SourceSpan.of(3, 0, 54, 4)), ghi.getSourceSpans()); Link three = (Link) ghi.getNext(); assertEquals("http://example.com/three", three.getDestination()); - assertEquals(List.of(SourceSpan.of(3, 4, 24)), + assertEquals(List.of(SourceSpan.of(3, 4, 58, 24)), three.getSourceSpans()); Text jkl = (Text) three.getNext(); assertEquals(" jkl", jkl.getLiteral()); - assertEquals(List.of(SourceSpan.of(3, 28, 4)), + assertEquals(List.of(SourceSpan.of(3, 28, 82, 4)), jkl.getSourceSpans()); } diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index 9090623cb..bef6d89cb 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -287,10 +287,10 @@ public void testSourcePositions() { var doc = parser.parse("Test [^foo]\n\n[^foo]: /url\n"); var ref = find(doc, FootnoteReference.class); - assertEquals(ref.getSourceSpans(), List.of(SourceSpan.of(0, 5, 6))); + assertEquals(ref.getSourceSpans(), List.of(SourceSpan.of(0, 5, 5, 6))); var def = find(doc, FootnoteDefinition.class); - assertEquals(def.getSourceSpans(), List.of(SourceSpan.of(2, 0, 12))); + assertEquals(def.getSourceSpans(), List.of(SourceSpan.of(2, 0, 13, 12))); } private static T find(Node parent, Class nodeClass) { diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java index de0a347f9..cb3019957 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java @@ -117,7 +117,7 @@ public void sourceSpans() { Node document = parser.parse("hey ~~there~~\n"); Paragraph block = (Paragraph) document.getFirstChild(); Node strikethrough = block.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 4, 9)), + assertEquals(List.of(SourceSpan.of(0, 4, 4, 9)), strikethrough.getSourceSpans()); } diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java index c2a8031d6..415a17815 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -791,45 +791,45 @@ public void sourceSpans() { Node document = parser.parse("Abc|Def\n---|---\n|1|2\n 3|four|\n|||\n"); TableBlock block = (TableBlock) document.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 7), - SourceSpan.of(2, 0, 4), SourceSpan.of(3, 0, 8), SourceSpan.of(4, 0, 3)), + assertEquals(List.of(SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 7), + SourceSpan.of(2, 0, 16, 4), SourceSpan.of(3, 0, 21, 8), SourceSpan.of(4, 0, 30, 3)), block.getSourceSpans()); TableHead head = (TableHead) block.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 7)), head.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 7)), head.getSourceSpans()); TableRow headRow = (TableRow) head.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 7)), headRow.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 7)), headRow.getSourceSpans()); TableCell headRowCell1 = (TableCell) headRow.getFirstChild(); TableCell headRowCell2 = (TableCell) headRow.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 3)), headRowCell1.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(0, 0, 3)), headRowCell1.getFirstChild().getSourceSpans()); - assertEquals(List.of(SourceSpan.of(0, 4, 3)), headRowCell2.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(0, 4, 3)), headRowCell2.getFirstChild().getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 3)), headRowCell1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 3)), headRowCell1.getFirstChild().getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 4, 4, 3)), headRowCell2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 4, 4, 3)), headRowCell2.getFirstChild().getSourceSpans()); TableBody body = (TableBody) block.getLastChild(); - assertEquals(List.of(SourceSpan.of(2, 0, 4), SourceSpan.of(3, 0, 8), SourceSpan.of(4, 0, 3)), body.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(2, 0, 16, 4), SourceSpan.of(3, 0, 21, 8), SourceSpan.of(4, 0, 30, 3)), body.getSourceSpans()); TableRow bodyRow1 = (TableRow) body.getFirstChild(); - assertEquals(List.of(SourceSpan.of(2, 0, 4)), bodyRow1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(2, 0, 16, 4)), bodyRow1.getSourceSpans()); TableCell bodyRow1Cell1 = (TableCell) bodyRow1.getFirstChild(); TableCell bodyRow1Cell2 = (TableCell) bodyRow1.getLastChild(); - assertEquals(List.of(SourceSpan.of(2, 1, 1)), bodyRow1Cell1.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(2, 1, 1)), bodyRow1Cell1.getFirstChild().getSourceSpans()); - assertEquals(List.of(SourceSpan.of(2, 3, 1)), bodyRow1Cell2.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(2, 3, 1)), bodyRow1Cell2.getFirstChild().getSourceSpans()); + assertEquals(List.of(SourceSpan.of(2, 1, 17, 1)), bodyRow1Cell1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(2, 1, 17, 1)), bodyRow1Cell1.getFirstChild().getSourceSpans()); + assertEquals(List.of(SourceSpan.of(2, 3, 19, 1)), bodyRow1Cell2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(2, 3, 19, 1)), bodyRow1Cell2.getFirstChild().getSourceSpans()); TableRow bodyRow2 = (TableRow) body.getFirstChild().getNext(); - assertEquals(List.of(SourceSpan.of(3, 0, 8)), bodyRow2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(3, 0, 21, 8)), bodyRow2.getSourceSpans()); TableCell bodyRow2Cell1 = (TableCell) bodyRow2.getFirstChild(); TableCell bodyRow2Cell2 = (TableCell) bodyRow2.getLastChild(); - assertEquals(List.of(SourceSpan.of(3, 1, 1)), bodyRow2Cell1.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(3, 1, 1)), bodyRow2Cell1.getFirstChild().getSourceSpans()); - assertEquals(List.of(SourceSpan.of(3, 3, 4)), bodyRow2Cell2.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(3, 3, 4)), bodyRow2Cell2.getFirstChild().getSourceSpans()); + assertEquals(List.of(SourceSpan.of(3, 1, 22, 1)), bodyRow2Cell1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(3, 1, 22, 1)), bodyRow2Cell1.getFirstChild().getSourceSpans()); + assertEquals(List.of(SourceSpan.of(3, 3, 24, 4)), bodyRow2Cell2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(3, 3, 24, 4)), bodyRow2Cell2.getFirstChild().getSourceSpans()); TableRow bodyRow3 = (TableRow) body.getLastChild(); - assertEquals(List.of(SourceSpan.of(4, 0, 3)), bodyRow3.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(4, 0, 30, 3)), bodyRow3.getSourceSpans()); TableCell bodyRow3Cell1 = (TableCell) bodyRow3.getFirstChild(); TableCell bodyRow3Cell2 = (TableCell) bodyRow3.getLastChild(); assertEquals(List.of(), bodyRow3Cell1.getSourceSpans()); diff --git a/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java b/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java index 112bf53d3..b863bd4b7 100644 --- a/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java +++ b/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java @@ -131,7 +131,7 @@ public void sourceSpans() { Node document = parser.parse("x{height=3 width=4}\n"); Paragraph block = (Paragraph) document.getFirstChild(); Node text = block.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 19)), + assertEquals(List.of(SourceSpan.of(0, 0, 0, 19)), text.getSourceSpans()); } diff --git a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java index 0fd8d512a..4603da60b 100644 --- a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java +++ b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java @@ -102,7 +102,7 @@ public void sourceSpans() { Node document = parser.parse("hey ++there++\n"); Paragraph block = (Paragraph) document.getFirstChild(); Node ins = block.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 4, 9)), + assertEquals(List.of(SourceSpan.of(0, 4, 4, 9)), ins.getSourceSpans()); } diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index d3f6f6811..7fd2d4300 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -1,5 +1,6 @@ package org.commonmark.internal; +import org.commonmark.internal.util.LineReader; import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.IncludeSourceSpans; @@ -127,7 +128,7 @@ public Document parse(String input) { int lineBreak; while ((lineBreak = Characters.findLineBreak(input, lineStart)) != -1) { String line = input.substring(lineStart, lineBreak); - parseLine(line); + parseLine(line, lineStart); if (lineBreak + 1 < input.length() && input.charAt(lineBreak) == '\r' && input.charAt(lineBreak + 1) == '\n') { lineStart = lineBreak + 2; } else { @@ -136,23 +137,23 @@ public Document parse(String input) { } if (!input.isEmpty() && (lineStart == 0 || lineStart < input.length())) { String line = input.substring(lineStart); - parseLine(line); + parseLine(line, lineStart); } return finalizeAndProcess(); } public Document parse(Reader input) throws IOException { - BufferedReader bufferedReader; - if (input instanceof BufferedReader) { - bufferedReader = (BufferedReader) input; - } else { - bufferedReader = new BufferedReader(input); - } - + var lineReader = new LineReader(input); + int inputIndex = 0; String line; - while ((line = bufferedReader.readLine()) != null) { - parseLine(line); + while ((line = lineReader.readLine()) != null) { + parseLine(line, inputIndex); + inputIndex += line.length(); + var eol = lineReader.getLineTerminator(); + if (eol != null) { + inputIndex += eol.length(); + } } return finalizeAndProcess(); @@ -197,8 +198,8 @@ public BlockParser getActiveBlockParser() { * Analyze a line of text and update the document appropriately. We parse markdown text by calling this on each * line of input, then finalizing the document. */ - private void parseLine(String ln) { - setLine(ln); + private void parseLine(String ln, int inputIndex) { + setLine(ln, inputIndex); // For each containing block, try to parse the associated line start. // The document will always match, so we can skip the first block parser and start at 1 matches @@ -322,7 +323,7 @@ private void parseLine(String ln) { } } - private void setLine(String ln) { + private void setLine(String ln, int inputIndex) { lineIndex++; index = 0; column = 0; @@ -331,7 +332,7 @@ private void setLine(String ln) { String lineContent = prepareLine(ln); SourceSpan sourceSpan = null; if (includeSourceSpans != IncludeSourceSpans.NONE) { - sourceSpan = SourceSpan.of(lineIndex, 0, lineContent.length()); + sourceSpan = SourceSpan.of(lineIndex, 0, inputIndex, lineContent.length()); } this.line = SourceLine.of(lineContent, sourceSpan); } @@ -430,10 +431,9 @@ private void addLine() { content = line.getContent().subSequence(index, line.getContent().length()); } SourceSpan sourceSpan = null; - if (includeSourceSpans == IncludeSourceSpans.BLOCKS_AND_INLINES) { - // Note that if we're in a partially-consumed tab, the length here corresponds to the content but not to the - // actual source length. That sounds like a problem, but I haven't found a test case where it matters (yet). - sourceSpan = SourceSpan.of(lineIndex, index, content.length()); + if (includeSourceSpans == IncludeSourceSpans.BLOCKS_AND_INLINES && index < line.getSourceSpan().getLength()) { + // Note that if we're in a partially-consumed tab the length of the source span and the content don't match. + sourceSpan = line.getSourceSpan().subSpan(index); } getActiveBlockParser().addLine(SourceLine.of(content, sourceSpan)); addSourceSpans(); @@ -449,7 +449,7 @@ private void addSourceSpans() { int blockIndex = Math.min(openBlockParser.sourceIndex, index); int length = line.getContent().length() - blockIndex; if (length != 0) { - openBlockParser.blockParser.addSourceSpan(SourceSpan.of(lineIndex, blockIndex, length)); + openBlockParser.blockParser.addSourceSpan(line.getSourceSpan().subSpan(blockIndex)); } } } diff --git a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java index f7dbabc27..6558cc84a 100644 --- a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java +++ b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java @@ -27,32 +27,64 @@ public class SourceSpan { private final int lineIndex; private final int columnIndex; + private final int inputIndex; private final int length; + public static SourceSpan of(int line, int col, int input, int length) { + return new SourceSpan(line, col, input, length); + } + + /** + * @deprecated Use {{@link #of(int, int, int, int)}} instead to also specify input index. Using the deprecated one + * will set {@link #inputIndex} to 0. + */ + @Deprecated public static SourceSpan of(int lineIndex, int columnIndex, int length) { - return new SourceSpan(lineIndex, columnIndex, length); + return of(lineIndex, columnIndex, 0, length); } - private SourceSpan(int lineIndex, int columnIndex, int length) { + private SourceSpan(int lineIndex, int columnIndex, int inputIndex, int length) { + if (lineIndex < 0) { + throw new IllegalArgumentException("lineIndex " + lineIndex + " must be >= 0"); + } + if (columnIndex < 0) { + throw new IllegalArgumentException("columnIndex " + columnIndex + " must be >= 0"); + } + if (inputIndex < 0) { + throw new IllegalArgumentException("inputIndex " + inputIndex + " must be >= 0"); + } + if (length < 0) { + throw new IllegalArgumentException("length " + length + " must be >= 0"); + } this.lineIndex = lineIndex; this.columnIndex = columnIndex; + this.inputIndex = inputIndex; this.length = length; } /** - * @return 0-based index of line in source + * @return 0-based line index, e.g. 0 for first line, 1 for the second line, etc */ public int getLineIndex() { return lineIndex; } /** - * @return 0-based index of column (character on line) in source + * @return 0-based index of column (character on line) in source, e.g. 0 for the first character of a line, 1 for + * the second character, etc */ public int getColumnIndex() { return columnIndex; } + /** + * @return 0-based index in whole input + * @since 0.24.0 + */ + public int getInputIndex() { + return inputIndex; + } + /** * @return length of the span in characters */ @@ -60,6 +92,32 @@ public int getLength() { return length; } + public SourceSpan subSpan(int beginIndex) { + return subSpan(beginIndex, length); + } + + public SourceSpan subSpan(int beginIndex, int endIndex) { + if (beginIndex < 0) { + throw new IndexOutOfBoundsException("beginIndex " + beginIndex + " + must be >= 0"); + } + if (beginIndex > length) { + throw new IndexOutOfBoundsException("beginIndex " + beginIndex + " must be <= length " + length); + } + if (endIndex < 0) { + throw new IndexOutOfBoundsException("endIndex " + endIndex + " + must be >= 0"); + } + if (endIndex > length) { + throw new IndexOutOfBoundsException("endIndex " + endIndex + " must be <= length " + length); + } + if (beginIndex > endIndex) { + throw new IndexOutOfBoundsException("beginIndex " + beginIndex + " must be <= endIndex " + endIndex); + } + if (beginIndex == 0 && endIndex == length) { + return this; + } + return new SourceSpan(lineIndex, columnIndex + beginIndex, inputIndex + beginIndex, endIndex - beginIndex); + } + @Override public boolean equals(Object o) { if (this == o) { @@ -71,12 +129,13 @@ public boolean equals(Object o) { SourceSpan that = (SourceSpan) o; return lineIndex == that.lineIndex && columnIndex == that.columnIndex && + inputIndex == that.inputIndex && length == that.length; } @Override public int hashCode() { - return Objects.hash(lineIndex, columnIndex, length); + return Objects.hash(lineIndex, columnIndex, inputIndex, length); } @Override @@ -84,6 +143,7 @@ public String toString() { return "SourceSpan{" + "line=" + lineIndex + ", column=" + columnIndex + + ", input=" + inputIndex + ", length=" + length + "}"; } diff --git a/commonmark/src/main/java/org/commonmark/node/SourceSpans.java b/commonmark/src/main/java/org/commonmark/node/SourceSpans.java index 5118dea75..975d7fbdb 100644 --- a/commonmark/src/main/java/org/commonmark/node/SourceSpans.java +++ b/commonmark/src/main/java/org/commonmark/node/SourceSpans.java @@ -41,8 +41,8 @@ public void addAll(List other) { int lastIndex = sourceSpans.size() - 1; SourceSpan a = sourceSpans.get(lastIndex); SourceSpan b = other.get(0); - if (a.getLineIndex() == b.getLineIndex() && a.getColumnIndex() + a.getLength() == b.getColumnIndex()) { - sourceSpans.set(lastIndex, SourceSpan.of(a.getLineIndex(), a.getColumnIndex(), a.getLength() + b.getLength())); + if (a.getInputIndex() + a.getLength() == b.getInputIndex()) { + sourceSpans.set(lastIndex, SourceSpan.of(a.getLineIndex(), a.getColumnIndex(), a.getInputIndex(), a.getLength() + b.getLength())); sourceSpans.addAll(other.subList(1, other.size())); } else { sourceSpans.addAll(other); diff --git a/commonmark/src/main/java/org/commonmark/parser/SourceLine.java b/commonmark/src/main/java/org/commonmark/parser/SourceLine.java index d73b14ce2..92a8cdfaf 100644 --- a/commonmark/src/main/java/org/commonmark/parser/SourceLine.java +++ b/commonmark/src/main/java/org/commonmark/parser/SourceLine.java @@ -35,10 +35,11 @@ public SourceLine substring(int beginIndex, int endIndex) { CharSequence newContent = content.subSequence(beginIndex, endIndex); SourceSpan newSourceSpan = null; if (sourceSpan != null) { - int columnIndex = sourceSpan.getColumnIndex() + beginIndex; int length = endIndex - beginIndex; if (length != 0) { - newSourceSpan = SourceSpan.of(sourceSpan.getLineIndex(), columnIndex, length); + int columnIndex = sourceSpan.getColumnIndex() + beginIndex; + int inputIndex = sourceSpan.getInputIndex() + beginIndex; + newSourceSpan = SourceSpan.of(sourceSpan.getLineIndex(), columnIndex, inputIndex, length); } } return SourceLine.of(newContent, newSourceSpan); diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/Scanner.java b/commonmark/src/main/java/org/commonmark/parser/beta/Scanner.java index 482f8eb2a..324639493 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/Scanner.java @@ -244,7 +244,7 @@ public SourceLines getSource(Position begin, Position end) { SourceSpan newSourceSpan = null; SourceSpan sourceSpan = line.getSourceSpan(); if (sourceSpan != null) { - newSourceSpan = SourceSpan.of(sourceSpan.getLineIndex(), sourceSpan.getColumnIndex() + begin.index, newContent.length()); + newSourceSpan = sourceSpan.subSpan(begin.index, end.index); } return SourceLines.of(SourceLine.of(newContent, newSourceSpan)); } else { diff --git a/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java b/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java index df8c51758..14c118ab9 100644 --- a/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java @@ -85,8 +85,8 @@ public void testCodePoints() { @Test public void testTextBetween() { Scanner scanner = new Scanner(List.of( - SourceLine.of("ab", SourceSpan.of(10, 3, 2)), - SourceLine.of("cde", SourceSpan.of(11, 4, 3))), + SourceLine.of("ab", SourceSpan.of(10, 3, 13, 2)), + SourceLine.of("cde", SourceSpan.of(11, 4, 20, 3))), 0, 0); Position start = scanner.position(); @@ -94,48 +94,48 @@ public void testTextBetween() { scanner.next(); assertSourceLines(scanner.getSource(start, scanner.position()), "a", - SourceSpan.of(10, 3, 1)); + SourceSpan.of(10, 3, 13, 1)); Position afterA = scanner.position(); scanner.next(); assertSourceLines(scanner.getSource(start, scanner.position()), "ab", - SourceSpan.of(10, 3, 2)); + SourceSpan.of(10, 3, 13, 2)); Position afterB = scanner.position(); scanner.next(); assertSourceLines(scanner.getSource(start, scanner.position()), "ab\n", - SourceSpan.of(10, 3, 2)); + SourceSpan.of(10, 3, 13, 2)); scanner.next(); assertSourceLines(scanner.getSource(start, scanner.position()), "ab\nc", - SourceSpan.of(10, 3, 2), - SourceSpan.of(11, 4, 1)); + SourceSpan.of(10, 3, 13, 2), + SourceSpan.of(11, 4, 20, 1)); scanner.next(); assertSourceLines(scanner.getSource(start, scanner.position()), "ab\ncd", - SourceSpan.of(10, 3, 2), - SourceSpan.of(11, 4, 2)); + SourceSpan.of(10, 3, 13, 2), + SourceSpan.of(11, 4, 20, 2)); scanner.next(); assertSourceLines(scanner.getSource(start, scanner.position()), "ab\ncde", - SourceSpan.of(10, 3, 2), - SourceSpan.of(11, 4, 3)); + SourceSpan.of(10, 3, 13, 2), + SourceSpan.of(11, 4, 20, 3)); assertSourceLines(scanner.getSource(afterA, scanner.position()), "b\ncde", - SourceSpan.of(10, 4, 1), - SourceSpan.of(11, 4, 3)); + SourceSpan.of(10, 4, 14, 1), + SourceSpan.of(11, 4, 20, 3)); assertSourceLines(scanner.getSource(afterB, scanner.position()), "\ncde", - SourceSpan.of(11, 4, 3)); + SourceSpan.of(11, 4, 20, 3)); } private void assertSourceLines(SourceLines sourceLines, String expectedContent, SourceSpan... expectedSourceSpans) { diff --git a/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java b/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java index aa330fbc9..3fb95d386 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java @@ -10,29 +10,29 @@ public class SourceLineTest { @Test public void testSubstring() { - SourceLine line = SourceLine.of("abcd", SourceSpan.of(3, 10, 4)); + SourceLine line = SourceLine.of("abcd", SourceSpan.of(3, 10, 13, 4)); - assertSourceLine(line.substring(0, 4), "abcd", SourceSpan.of(3, 10, 4)); - assertSourceLine(line.substring(0, 3), "abc", SourceSpan.of(3, 10, 3)); - assertSourceLine(line.substring(0, 2), "ab", SourceSpan.of(3, 10, 2)); - assertSourceLine(line.substring(0, 1), "a", SourceSpan.of(3, 10, 1)); + assertSourceLine(line.substring(0, 4), "abcd", SourceSpan.of(3, 10, 13, 4)); + assertSourceLine(line.substring(0, 3), "abc", SourceSpan.of(3, 10, 13, 3)); + assertSourceLine(line.substring(0, 2), "ab", SourceSpan.of(3, 10, 13, 2)); + assertSourceLine(line.substring(0, 1), "a", SourceSpan.of(3, 10, 13, 1)); assertSourceLine(line.substring(0, 0), "", null); - assertSourceLine(line.substring(1, 4), "bcd", SourceSpan.of(3, 11, 3)); - assertSourceLine(line.substring(1, 3), "bc", SourceSpan.of(3, 11, 2)); + assertSourceLine(line.substring(1, 4), "bcd", SourceSpan.of(3, 11, 14, 3)); + assertSourceLine(line.substring(1, 3), "bc", SourceSpan.of(3, 11, 14, 2)); - assertSourceLine(line.substring(3, 4), "d", SourceSpan.of(3, 13, 1)); + assertSourceLine(line.substring(3, 4), "d", SourceSpan.of(3, 13, 16, 1)); assertSourceLine(line.substring(4, 4), "", null); } @Test(expected = StringIndexOutOfBoundsException.class) public void testSubstringBeginOutOfBounds() { - SourceLine.of("abcd", SourceSpan.of(3, 10, 4)).substring(3, 2); + SourceLine.of("abcd", SourceSpan.of(3, 10, 13, 4)).substring(3, 2); } @Test(expected = StringIndexOutOfBoundsException.class) public void testSubstringEndOutOfBounds() { - SourceLine.of("abcd", SourceSpan.of(3, 10, 4)).substring(0, 5); + SourceLine.of("abcd", SourceSpan.of(3, 10, 13, 4)).substring(0, 5); } private static void assertSourceLine(SourceLine sourceLine, String expectedContent, SourceSpan expectedSourceSpan) { diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java b/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java index 1b76ed5bd..5181a36e7 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java @@ -4,17 +4,17 @@ import org.commonmark.node.Node; import org.commonmark.node.SourceSpan; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; public class SourceSpanRenderer { - public static String render(Node document, String source) { + /** + * Render source spans in the document using source position's line and column index. + */ + public static String renderWithLineColumn(Node document, String source) { SourceSpanMarkersVisitor visitor = new SourceSpanMarkersVisitor(); document.accept(visitor); - Map>> markers = visitor.getMarkers(); + var lineColumnMarkers = visitor.getLineColumnMarkers(); StringBuilder sb = new StringBuilder(); @@ -22,7 +22,7 @@ public static String render(Node document, String source) { for (int lineIndex = 0; lineIndex < lines.length; lineIndex++) { String line = lines[lineIndex]; - Map> lineMarkers = markers.get(lineIndex); + Map> lineMarkers = lineColumnMarkers.get(lineIndex); for (int i = 0; i < line.length(); i++) { appendMarkers(lineMarkers, i, sb); sb.append(line.charAt(i)); @@ -34,6 +34,22 @@ public static String render(Node document, String source) { return sb.toString(); } + /** + * Render source spans in the document using source position's input index. + */ + public static String renderWithInputIndex(Node document, String source) { + SourceSpanMarkersVisitor visitor = new SourceSpanMarkersVisitor(); + document.accept(visitor); + var markers = visitor.getInputIndexMarkers(); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < source.length(); i++) { + markers.getOrDefault(i, List.of()).forEach(marker -> sb.append(marker)); + sb.append(source.charAt(i)); + } + return sb.toString(); + } + private static void appendMarkers(Map> lineMarkers, int columnIndex, StringBuilder sb) { if (lineMarkers != null) { List columnMarkers = lineMarkers.get(columnIndex); @@ -50,24 +66,35 @@ private static class SourceSpanMarkersVisitor extends AbstractVisitor { private static final String OPENING = "({[<⸢⸤"; private static final String CLOSING = ")}]>⸣⸥"; - private final Map>> markers = new HashMap<>(); + private final Map>> lineColumnMarkers = new HashMap<>(); + private final Map> inputIndexMarkers = new HashMap<>(); private int markerIndex; - public Map>> getMarkers() { - return markers; + public Map>> getLineColumnMarkers() { + return lineColumnMarkers; + } + + public Map> getInputIndexMarkers() { + return inputIndexMarkers; } @Override protected void visitChildren(Node parent) { if (!parent.getSourceSpans().isEmpty()) { - for (SourceSpan sourceSpan : parent.getSourceSpans()) { + for (var span : parent.getSourceSpans()) { String opener = String.valueOf(OPENING.charAt(markerIndex % OPENING.length())); String closer = String.valueOf(CLOSING.charAt(markerIndex % CLOSING.length())); - int col = sourceSpan.getColumnIndex(); - getMarkers(sourceSpan.getLineIndex(), col).add(opener); - getMarkers(sourceSpan.getLineIndex(), col + sourceSpan.getLength()).add(0, closer); + int line = span.getLineIndex(); + int col = span.getColumnIndex(); + var input = span.getInputIndex(); + int length = span.getLength(); + getMarkers(line, col).add(opener); + getMarkers(line, col + length).add(0, closer); + + inputIndexMarkers.computeIfAbsent(input, k -> new LinkedList<>()).add(opener); + inputIndexMarkers.computeIfAbsent(input + length, k -> new LinkedList<>()).add(0, closer); } markerIndex++; } @@ -75,19 +102,8 @@ protected void visitChildren(Node parent) { } private List getMarkers(int lineIndex, int columnIndex) { - Map> columnMap = markers.get(lineIndex); - if (columnMap == null) { - columnMap = new HashMap<>(); - markers.put(lineIndex, columnMap); - } - - List markers = columnMap.get(columnIndex); - if (markers == null) { - markers = new LinkedList<>(); - columnMap.put(columnIndex, markers); - } - - return markers; + var columnMap = lineColumnMarkers.computeIfAbsent(lineIndex, k -> new HashMap<>()); + return columnMap.computeIfAbsent(columnIndex, k -> new LinkedList<>()); } } } diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpanTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpanTest.java new file mode 100644 index 000000000..57048b90e --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpanTest.java @@ -0,0 +1,63 @@ +package org.commonmark.test; + +import org.commonmark.node.SourceSpan; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +public class SourceSpanTest { + + @Test + public void testSubSpan() { + var span = SourceSpan.of(1, 2, 3, 5); + + assertSame(span.subSpan(0), span); + assertSame(span.subSpan(0, 5), span); + + assertEquals(SourceSpan.of(1, 3, 4, 4), span.subSpan(1)); + assertEquals(SourceSpan.of(1, 4, 5, 3), span.subSpan(2)); + assertEquals(SourceSpan.of(1, 5, 6, 2), span.subSpan(3)); + assertEquals(SourceSpan.of(1, 6, 7, 1), span.subSpan(4)); + // Not sure if empty spans are useful, but it probably makes sense to mirror how substrings work + assertEquals(SourceSpan.of(1, 7, 8, 0), span.subSpan(5)); + assertEquals("", "abcde".substring(5)); + + assertEquals(SourceSpan.of(1, 2, 3, 5), span.subSpan(0, 5)); + assertEquals(SourceSpan.of(1, 2, 3, 4), span.subSpan(0, 4)); + assertEquals(SourceSpan.of(1, 2, 3, 3), span.subSpan(0, 3)); + assertEquals(SourceSpan.of(1, 2, 3, 2), span.subSpan(0, 2)); + assertEquals(SourceSpan.of(1, 2, 3, 1), span.subSpan(0, 1)); + assertEquals(SourceSpan.of(1, 2, 3, 0), span.subSpan(0, 0)); + assertEquals("a", "abcde".substring(0, 1)); + assertEquals("", "abcde".substring(0, 0)); + + assertEquals(SourceSpan.of(1, 3, 4, 3), span.subSpan(1, 4)); + assertEquals(SourceSpan.of(1, 4, 5, 1), span.subSpan(2, 3)); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void testSubSpanBeginIndexNegative() { + SourceSpan.of(1, 2, 3, 5).subSpan(-1); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void testSubSpanBeginIndexOutOfBounds() { + SourceSpan.of(1, 2, 3, 5).subSpan(6); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void testSubSpanEndIndexNegative() { + SourceSpan.of(1, 2, 3, 5).subSpan(0, -1); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void testSubSpanEndIndexOutOfBounds() { + SourceSpan.of(1, 2, 3, 5).subSpan(0, 6); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void testSubSpanBeginIndexGreaterThanEndIndex() { + SourceSpan.of(1, 2, 3, 5).subSpan(2, 1); + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index eb7e8f005..23249d930 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -5,6 +5,8 @@ import org.commonmark.parser.Parser; import org.junit.Test; +import java.io.IOException; +import java.io.StringReader; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; @@ -18,137 +20,135 @@ public class SourceSpansTest { @Test public void paragraph() { - assertSpans("foo\n", Paragraph.class, SourceSpan.of(0, 0, 3)); - assertSpans("foo\nbar\n", Paragraph.class, SourceSpan.of(0, 0, 3), SourceSpan.of(1, 0, 3)); - assertSpans(" foo\n bar\n", Paragraph.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 5)); - assertSpans("> foo\n> bar\n", Paragraph.class, SourceSpan.of(0, 2, 3), SourceSpan.of(1, 2, 3)); - assertSpans("* foo\n bar\n", Paragraph.class, SourceSpan.of(0, 2, 3), SourceSpan.of(1, 2, 3)); - assertSpans("* foo\nbar\n", Paragraph.class, SourceSpan.of(0, 2, 3), SourceSpan.of(1, 0, 3)); + assertSpans("foo\n", Paragraph.class, SourceSpan.of(0, 0, 0, 3)); + assertSpans("foo\nbar\n", Paragraph.class, SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 3)); + assertSpans(" foo\n bar\n", Paragraph.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 5)); + assertSpans("> foo\n> bar\n", Paragraph.class, SourceSpan.of(0, 2, 2, 3), SourceSpan.of(1, 2, 8, 3)); + assertSpans("* foo\n bar\n", Paragraph.class, SourceSpan.of(0, 2, 2, 3), SourceSpan.of(1, 2, 8, 3)); + assertSpans("* foo\nbar\n", Paragraph.class, SourceSpan.of(0, 2, 2, 3), SourceSpan.of(1, 0, 6, 3)); } @Test public void thematicBreak() { - assertSpans("---\n", ThematicBreak.class, SourceSpan.of(0, 0, 3)); - assertSpans(" ---\n", ThematicBreak.class, SourceSpan.of(0, 0, 5)); - assertSpans("> ---\n", ThematicBreak.class, SourceSpan.of(0, 2, 3)); + assertSpans("---\n", ThematicBreak.class, SourceSpan.of(0, 0, 0, 3)); + assertSpans(" ---\n", ThematicBreak.class, SourceSpan.of(0, 0, 0, 5)); + assertSpans("> ---\n", ThematicBreak.class, SourceSpan.of(0, 2, 2, 3)); } @Test public void atxHeading() { - assertSpans("# foo", Heading.class, SourceSpan.of(0, 0, 5)); - assertSpans(" # foo", Heading.class, SourceSpan.of(0, 0, 6)); - assertSpans("## foo ##", Heading.class, SourceSpan.of(0, 0, 9)); - assertSpans("> # foo", Heading.class, SourceSpan.of(0, 2, 5)); + assertSpans("# foo", Heading.class, SourceSpan.of(0, 0, 0, 5)); + assertSpans(" # foo", Heading.class, SourceSpan.of(0, 0, 0, 6)); + assertSpans("## foo ##", Heading.class, SourceSpan.of(0, 0, 0, 9)); + assertSpans("> # foo", Heading.class, SourceSpan.of(0, 2, 2, 5)); } @Test public void setextHeading() { - assertSpans("foo\n===\n", Heading.class, SourceSpan.of(0, 0, 3), SourceSpan.of(1, 0, 3)); - assertSpans("foo\nbar\n====\n", Heading.class, SourceSpan.of(0, 0, 3), SourceSpan.of(1, 0, 3), SourceSpan.of(2, 0, 4)); - assertSpans(" foo\n ===\n", Heading.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 5)); - assertSpans("> foo\n> ===\n", Heading.class, SourceSpan.of(0, 2, 3), SourceSpan.of(1, 2, 3)); + assertSpans("foo\n===\n", Heading.class, SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 3)); + assertSpans("foo\nbar\n====\n", Heading.class, SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 3), SourceSpan.of(2, 0, 8, 4)); + assertSpans(" foo\n ===\n", Heading.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 5)); + assertSpans("> foo\n> ===\n", Heading.class, SourceSpan.of(0, 2, 2, 3), SourceSpan.of(1, 2, 8, 3)); } @Test public void indentedCodeBlock() { - assertSpans(" foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 7)); - assertSpans(" foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 8)); - assertSpans("\tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 4)); - assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 5)); - assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 6)); - assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 7)); - assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 8)); - assertSpans(" \t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 9)); - assertSpans("\t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 5)); - assertSpans("\t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 6)); - assertSpans(" foo\n bar\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 8)); - assertSpans(" foo\n\tbar\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 4)); - assertSpans(" foo\n \n \n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 4), SourceSpan.of(2, 0, 5)); - assertSpans("> foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 2, 7)); + assertSpans(" foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 7)); + assertSpans(" foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 8)); + assertSpans("\tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 4)); + assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 5)); + assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 6)); + assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 7)); + assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 8)); + assertSpans(" \t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 9)); + assertSpans("\t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 5)); + assertSpans("\t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 6)); + assertSpans(" foo\n bar\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 8)); + assertSpans(" foo\n\tbar\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 4)); + assertSpans(" foo\n \n \n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 4), SourceSpan.of(2, 0, 13, 5)); + assertSpans("> foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 2, 2, 7)); } @Test public void fencedCodeBlock() { assertSpans("```\nfoo\n```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 3), SourceSpan.of(1, 0, 3), SourceSpan.of(2, 0, 3)); + SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 3), SourceSpan.of(2, 0, 8, 3)); assertSpans("```\n foo\n```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 3), SourceSpan.of(1, 0, 4), SourceSpan.of(2, 0, 3)); + SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 4), SourceSpan.of(2, 0, 9, 3)); assertSpans("```\nfoo\nbar\n```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 3), SourceSpan.of(1, 0, 3), SourceSpan.of(2, 0, 3), SourceSpan.of(3, 0, 3)); - assertSpans("```\nfoo\nbar\n```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 3), SourceSpan.of(1, 0, 3), SourceSpan.of(2, 0, 3), SourceSpan.of(3, 0, 3)); + SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 3), SourceSpan.of(2, 0, 8, 3), SourceSpan.of(3, 0, 12, 3)); assertSpans(" ```\n foo\n ```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 6), SourceSpan.of(1, 0, 6), SourceSpan.of(2, 0, 6)); + SourceSpan.of(0, 0, 0, 6), SourceSpan.of(1, 0, 7, 6), SourceSpan.of(2, 0, 14, 6)); assertSpans(" ```\n foo\nfoo\n```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 4), SourceSpan.of(1, 0, 4), SourceSpan.of(2, 0, 3), SourceSpan.of(3, 0, 3)); + SourceSpan.of(0, 0, 0, 4), SourceSpan.of(1, 0, 5, 4), SourceSpan.of(2, 0, 10, 3), SourceSpan.of(3, 0, 14, 3)); assertSpans("```info\nfoo\n```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 3), SourceSpan.of(2, 0, 3)); + SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 3), SourceSpan.of(2, 0, 12, 3)); assertSpans("* ```\n foo\n ```\n", FencedCodeBlock.class, - SourceSpan.of(0, 2, 3), SourceSpan.of(1, 2, 3), SourceSpan.of(2, 2, 3)); + SourceSpan.of(0, 2, 2, 3), SourceSpan.of(1, 2, 8, 3), SourceSpan.of(2, 2, 14, 3)); assertSpans("> ```\n> foo\n> ```\n", FencedCodeBlock.class, - SourceSpan.of(0, 2, 3), SourceSpan.of(1, 2, 3), SourceSpan.of(2, 2, 3)); + SourceSpan.of(0, 2, 2, 3), SourceSpan.of(1, 2, 8, 3), SourceSpan.of(2, 2, 14, 3)); Node document = PARSER.parse("```\nfoo\n```\nbar\n"); Paragraph paragraph = (Paragraph) document.getLastChild(); - assertEquals(List.of(SourceSpan.of(3, 0, 3)), paragraph.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(3, 0, 12, 3)), paragraph.getSourceSpans()); } @Test public void htmlBlock() { - assertSpans("

    \n", HtmlBlock.class, SourceSpan.of(0, 0, 5)); + assertSpans("
    \n", HtmlBlock.class, SourceSpan.of(0, 0, 0, 5)); assertSpans("
    \n foo\n
    \n", HtmlBlock.class, - SourceSpan.of(0, 0, 6), - SourceSpan.of(1, 0, 4), - SourceSpan.of(2, 0, 7)); - assertSpans("*
    \n", HtmlBlock.class, SourceSpan.of(0, 2, 5)); + SourceSpan.of(0, 0, 0, 6), + SourceSpan.of(1, 0, 7, 4), + SourceSpan.of(2, 0, 12, 7)); + assertSpans("*
    \n", HtmlBlock.class, SourceSpan.of(0, 2, 2, 5)); } @Test public void blockQuote() { - assertSpans(">foo\n", BlockQuote.class, SourceSpan.of(0, 0, 4)); - assertSpans("> foo\n", BlockQuote.class, SourceSpan.of(0, 0, 5)); - assertSpans("> foo\n", BlockQuote.class, SourceSpan.of(0, 0, 6)); - assertSpans(" > foo\n", BlockQuote.class, SourceSpan.of(0, 0, 6)); - assertSpans(" > foo\n > bar\n", BlockQuote.class, SourceSpan.of(0, 0, 8), SourceSpan.of(1, 0, 7)); + assertSpans(">foo\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 4)); + assertSpans("> foo\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 5)); + assertSpans("> foo\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 6)); + assertSpans(" > foo\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 6)); + assertSpans(" > foo\n > bar\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 8), SourceSpan.of(1, 0, 9, 7)); // Lazy continuations - assertSpans("> foo\nbar\n", BlockQuote.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 3)); - assertSpans("> foo\nbar\n> baz\n", BlockQuote.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 3), SourceSpan.of(2, 0, 5)); - assertSpans("> > foo\nbar\n", BlockQuote.class, SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 3)); + assertSpans("> foo\nbar\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 3)); + assertSpans("> foo\nbar\n> baz\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 3), SourceSpan.of(2, 0, 10, 5)); + assertSpans("> > foo\nbar\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 3)); } @Test public void listBlock() { - assertSpans("* foo\n", ListBlock.class, SourceSpan.of(0, 0, 5)); - assertSpans("* foo\n bar\n", ListBlock.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 5)); - assertSpans("* foo\n* bar\n", ListBlock.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 5)); - assertSpans("* foo\n # bar\n", ListBlock.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 7)); - assertSpans("* foo\n * bar\n", ListBlock.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 7)); - assertSpans("* foo\n> bar\n", ListBlock.class, SourceSpan.of(0, 0, 5)); - assertSpans("> * foo\n", ListBlock.class, SourceSpan.of(0, 2, 5)); + assertSpans("* foo\n", ListBlock.class, SourceSpan.of(0, 0, 0, 5)); + assertSpans("* foo\n bar\n", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 5)); + assertSpans("* foo\n* bar\n", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 5)); + assertSpans("* foo\n # bar\n", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 7)); + assertSpans("* foo\n * bar\n", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 7)); + assertSpans("* foo\n> bar\n", ListBlock.class, SourceSpan.of(0, 0, 0, 5)); + assertSpans("> * foo\n", ListBlock.class, SourceSpan.of(0, 2, 2, 5)); // Lazy continuations - assertSpans("* foo\nbar\nbaz", ListBlock.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 3), SourceSpan.of(2, 0, 3)); - assertSpans("* foo\nbar\n* baz", ListBlock.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 3), SourceSpan.of(2, 0, 5)); - assertSpans("* foo\n * bar\nbaz", ListBlock.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 7), SourceSpan.of(2, 0, 3)); + assertSpans("* foo\nbar\nbaz", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 3), SourceSpan.of(2, 0, 10, 3)); + assertSpans("* foo\nbar\n* baz", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 3), SourceSpan.of(2, 0, 10, 5)); + assertSpans("* foo\n * bar\nbaz", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 7), SourceSpan.of(2, 0, 14, 3)); Node document = PARSER.parse("* foo\n * bar\n"); ListBlock listBlock = (ListBlock) document.getFirstChild().getFirstChild().getLastChild(); - assertEquals(List.of(SourceSpan.of(1, 2, 5)), listBlock.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(1, 2, 8, 5)), listBlock.getSourceSpans()); } @Test public void listItem() { - assertSpans("* foo\n", ListItem.class, SourceSpan.of(0, 0, 5)); - assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 0, 6)); - assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 0, 7)); - assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 0, 8)); - assertSpans("*\n foo\n", ListItem.class, SourceSpan.of(0, 0, 1), SourceSpan.of(1, 0, 5)); - assertSpans("*\n foo\n bar\n", ListItem.class, SourceSpan.of(0, 0, 1), SourceSpan.of(1, 0, 5), SourceSpan.of(2, 0, 5)); - assertSpans("> * foo\n", ListItem.class, SourceSpan.of(0, 2, 5)); + assertSpans("* foo\n", ListItem.class, SourceSpan.of(0, 0, 0, 5)); + assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 0, 0, 6)); + assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 0, 0, 7)); + assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 0, 0, 8)); + assertSpans("*\n foo\n", ListItem.class, SourceSpan.of(0, 0, 0, 1), SourceSpan.of(1, 0, 2, 5)); + assertSpans("*\n foo\n bar\n", ListItem.class, SourceSpan.of(0, 0, 0, 1), SourceSpan.of(1, 0, 2, 5), SourceSpan.of(2, 0, 8, 5)); + assertSpans("> * foo\n", ListItem.class, SourceSpan.of(0, 2, 2, 5)); // Lazy continuations - assertSpans("* foo\nbar\n", ListItem.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 3)); - assertSpans("* foo\nbar\nbaz\n", ListItem.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 3), SourceSpan.of(2, 0, 3)); + assertSpans("* foo\nbar\n", ListItem.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 3)); + assertSpans("* foo\nbar\nbaz\n", ListItem.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 3), SourceSpan.of(2, 0, 10, 3)); } @Test @@ -158,10 +158,10 @@ public void linkReferenceDefinition() { Node document = PARSER.parse("[foo]: /url\ntext\n"); LinkReferenceDefinition linkReferenceDefinition = (LinkReferenceDefinition) document.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 11)), linkReferenceDefinition.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 11)), linkReferenceDefinition.getSourceSpans()); Paragraph paragraph = (Paragraph) document.getLastChild(); - assertEquals(List.of(SourceSpan.of(1, 0, 4)), paragraph.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(1, 0, 12, 4)), paragraph.getSourceSpans()); } @Test @@ -169,8 +169,8 @@ public void linkReferenceDefinitionMultiple() { var doc = PARSER.parse("[foo]: /foo\n[bar]: /bar\n"); var def1 = (LinkReferenceDefinition) doc.getFirstChild(); var def2 = (LinkReferenceDefinition) doc.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 11)), def1.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(1, 0, 11)), def2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 11)), def1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(1, 0, 12, 11)), def2.getSourceSpans()); } @Test @@ -178,8 +178,8 @@ public void linkReferenceDefinitionWithTitle() { var doc = PARSER.parse("[1]: #not-code \"Text\"\n[foo]: /foo\n"); var def1 = (LinkReferenceDefinition) doc.getFirstChild(); var def2 = (LinkReferenceDefinition) doc.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 21)), def1.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(1, 0, 11)), def2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 21)), def1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(1, 0, 22, 11)), def2.getSourceSpans()); } @Test @@ -187,8 +187,8 @@ public void linkReferenceDefinitionWithTitleInvalid() { var doc = PARSER.parse("[foo]: /url\n\"title\" ok\n"); var def = Nodes.find(doc, LinkReferenceDefinition.class); var paragraph = Nodes.find(doc, Paragraph.class); - assertEquals(List.of(SourceSpan.of(0, 0, 11)), def.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(1, 0, 10)), paragraph.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 11)), def.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(1, 0, 12, 10)), paragraph.getSourceSpans()); } @Test @@ -198,10 +198,10 @@ public void linkReferenceDefinitionHeading() { Node document = PARSER.parse("[foo]: /url\nHeading\n===\n"); LinkReferenceDefinition linkReferenceDefinition = (LinkReferenceDefinition) document.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 11)), linkReferenceDefinition.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 11)), linkReferenceDefinition.getSourceSpans()); Heading heading = (Heading) document.getLastChild(); - assertEquals(List.of(SourceSpan.of(1, 0, 7), SourceSpan.of(2, 0, 3)), heading.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(1, 0, 12, 7), SourceSpan.of(2, 0, 20, 3)), heading.getSourceSpans()); } @Test @@ -212,13 +212,13 @@ public void lazyContinuationLines() { var doc = PARSER.parse("> > > foo\nbar\n"); var bq1 = (BlockQuote) doc.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 9), SourceSpan.of(1, 0, 3)), bq1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 9), SourceSpan.of(1, 0, 10, 3)), bq1.getSourceSpans()); var bq2 = (BlockQuote) bq1.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 2, 7), SourceSpan.of(1, 0, 3)), bq2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 2, 2, 7), SourceSpan.of(1, 0, 10, 3)), bq2.getSourceSpans()); var bq3 = (BlockQuote) bq2.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 4, 5), SourceSpan.of(1, 0, 3)), bq3.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 4, 4, 5), SourceSpan.of(1, 0, 10, 3)), bq3.getSourceSpans()); var paragraph = (Paragraph) bq3.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 6, 3), SourceSpan.of(1, 0, 3)), paragraph.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 6, 6, 3), SourceSpan.of(1, 0, 10, 3)), paragraph.getSourceSpans()); } { @@ -226,13 +226,13 @@ public void lazyContinuationLines() { var doc = PARSER.parse("> > > foo\nbars\n"); var bq1 = (BlockQuote) doc.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 9), SourceSpan.of(1, 0, 4)), bq1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 9), SourceSpan.of(1, 0, 10, 4)), bq1.getSourceSpans()); var bq2 = (BlockQuote) bq1.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 2, 7), SourceSpan.of(1, 0, 4)), bq2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 2, 2, 7), SourceSpan.of(1, 0, 10, 4)), bq2.getSourceSpans()); var bq3 = (BlockQuote) bq2.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 4, 5), SourceSpan.of(1, 0, 4)), bq3.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 4, 4, 5), SourceSpan.of(1, 0, 10, 4)), bq3.getSourceSpans()); var paragraph = (Paragraph) bq3.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 6, 3), SourceSpan.of(1, 0, 4)), paragraph.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 6, 6, 3), SourceSpan.of(1, 0, 10, 4)), paragraph.getSourceSpans()); } { @@ -240,15 +240,15 @@ public void lazyContinuationLines() { var doc = PARSER.parse("> 1. > Blockquote\ncontinued here."); var bq1 = (BlockQuote) doc.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 17), SourceSpan.of(1, 0, 15)), bq1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 17), SourceSpan.of(1, 0, 18, 15)), bq1.getSourceSpans()); var orderedList = (OrderedList) bq1.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 2, 15), SourceSpan.of(1, 0, 15)), orderedList.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 2, 2, 15), SourceSpan.of(1, 0, 18, 15)), orderedList.getSourceSpans()); var listItem = (ListItem) orderedList.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 2, 15), SourceSpan.of(1, 0, 15)), listItem.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 2, 2, 15), SourceSpan.of(1, 0, 18, 15)), listItem.getSourceSpans()); var bq2 = (BlockQuote) listItem.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 5, 12), SourceSpan.of(1, 0, 15)), bq2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 5, 5, 12), SourceSpan.of(1, 0, 18, 15)), bq2.getSourceSpans()); var paragraph = (Paragraph) bq2.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 7, 10), SourceSpan.of(1, 0, 15)), paragraph.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 7, 7, 10), SourceSpan.of(1, 0, 18, 15)), paragraph.getSourceSpans()); } { @@ -256,127 +256,151 @@ public void lazyContinuationLines() { var doc = PARSER.parse("> > foo\n> bar\n"); var bq1 = (BlockQuote) doc.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 5)), bq1.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 5)), bq1.getSourceSpans()); var bq2 = (BlockQuote) bq1.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 2, 5), SourceSpan.of(1, 2, 3)), bq2.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 2, 2, 5), SourceSpan.of(1, 2, 10, 3)), bq2.getSourceSpans()); var paragraph = (Paragraph) bq2.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 4, 3), SourceSpan.of(1, 2, 3)), paragraph.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 4, 4, 3), SourceSpan.of(1, 2, 10, 3)), paragraph.getSourceSpans()); } } @Test public void visualCheck() { - assertEquals("(> {[* ]})\n(> {[ ]})\n(> {⸢* ⸤baz⸥⸣})\n", - visualizeSourceSpans("> * foo\n> bar\n> * baz\n")); - assertEquals("(> {[* <```>]})\n(> {[ ]})\n(> {[ <```>]})\n", - visualizeSourceSpans("> * ```\n> foo\n> ```")); + assertVisualize("> * foo\n> bar\n> * baz\n", "(> {[* ]})\n(> {[ ]})\n(> {⸢* ⸤baz⸥⸣})\n"); + assertVisualize("> * ```\n> foo\n> ```\n", "(> {[* <```>]})\n(> {[ ]})\n(> {[ <```>]})\n"); } @Test public void inlineText() { - assertInlineSpans("foo", Text.class, SourceSpan.of(0, 0, 3)); - assertInlineSpans("> foo", Text.class, SourceSpan.of(0, 2, 3)); - assertInlineSpans("* foo", Text.class, SourceSpan.of(0, 2, 3)); + assertInlineSpans("foo", Text.class, SourceSpan.of(0, 0, 0, 3)); + assertInlineSpans("> foo", Text.class, SourceSpan.of(0, 2, 2, 3)); + assertInlineSpans("* foo", Text.class, SourceSpan.of(0, 2, 2, 3)); // SourceSpans should be merged: ` is a separate Text node while inline parsing and gets merged at the end - assertInlineSpans("foo`bar", Text.class, SourceSpan.of(0, 0, 7)); - assertInlineSpans("foo[bar", Text.class, SourceSpan.of(0, 0, 7)); + assertInlineSpans("foo`bar", Text.class, SourceSpan.of(0, 0, 0, 7)); + assertInlineSpans("foo[bar", Text.class, SourceSpan.of(0, 0, 0, 7)); + assertInlineSpans("> foo`bar", Text.class, SourceSpan.of(0, 2, 2, 7)); - assertInlineSpans("[foo](/url)", Text.class, SourceSpan.of(0, 1, 3)); - assertInlineSpans("*foo*", Text.class, SourceSpan.of(0, 1, 3)); + assertInlineSpans("[foo](/url)", Text.class, SourceSpan.of(0, 1, 1, 3)); + assertInlineSpans("*foo*", Text.class, SourceSpan.of(0, 1, 1, 3)); } @Test public void inlineHeading() { - assertInlineSpans("# foo", Text.class, SourceSpan.of(0, 2, 3)); - assertInlineSpans(" # foo", Text.class, SourceSpan.of(0, 3, 3)); - assertInlineSpans("> # foo", Text.class, SourceSpan.of(0, 4, 3)); + assertInlineSpans("# foo", Text.class, SourceSpan.of(0, 2, 2, 3)); + assertInlineSpans(" # foo", Text.class, SourceSpan.of(0, 3, 3, 3)); + assertInlineSpans("> # foo", Text.class, SourceSpan.of(0, 4, 4, 3)); } @Test public void inlineAutolink() { - assertInlineSpans("see ", Link.class, SourceSpan.of(0, 4, 21)); + assertInlineSpans("see ", Link.class, SourceSpan.of(0, 4, 4, 21)); } @Test public void inlineBackslash() { - assertInlineSpans("\\!", Text.class, SourceSpan.of(0, 0, 2)); + assertInlineSpans("\\!", Text.class, SourceSpan.of(0, 0, 0, 2)); } @Test public void inlineBackticks() { - assertInlineSpans("see `code`", Code.class, SourceSpan.of(0, 4, 6)); + assertInlineSpans("see `code`", Code.class, SourceSpan.of(0, 4, 4, 6)); assertInlineSpans("`multi\nline`", Code.class, - SourceSpan.of(0, 0, 6), - SourceSpan.of(1, 0, 5)); - assertInlineSpans("text ```", Text.class, SourceSpan.of(0, 0, 8)); + SourceSpan.of(0, 0, 0, 6), + SourceSpan.of(1, 0, 7, 5)); + assertInlineSpans("text ```", Text.class, SourceSpan.of(0, 0, 0, 8)); } @Test public void inlineEntity() { - assertInlineSpans("&", Text.class, SourceSpan.of(0, 0, 5)); + assertInlineSpans("&", Text.class, SourceSpan.of(0, 0, 0, 5)); } @Test public void inlineHtml() { - assertInlineSpans("hi there", HtmlInline.class, SourceSpan.of(0, 3, 8)); + assertInlineSpans("hi there", HtmlInline.class, SourceSpan.of(0, 3, 3, 8)); } @Test public void links() { - assertInlineSpans("[text](/url)", Link.class, SourceSpan.of(0, 0, 12)); - assertInlineSpans("[text](/url)", Text.class, SourceSpan.of(0, 1, 4)); + assertInlineSpans("\n[text](/url)", Link.class, SourceSpan.of(1, 0, 1, 12)); + assertInlineSpans("\n[text](/url)", Text.class, SourceSpan.of(1, 1, 2, 4)); - assertInlineSpans("[text]\n\n[text]: /url", Link.class, SourceSpan.of(0, 0, 6)); - assertInlineSpans("[text]\n\n[text]: /url", Text.class, SourceSpan.of(0, 1, 4)); - assertInlineSpans("[text][]\n\n[text]: /url", Link.class, SourceSpan.of(0, 0, 8)); - assertInlineSpans("[text][]\n\n[text]: /url", Text.class, SourceSpan.of(0, 1, 4)); - assertInlineSpans("[text][ref]\n\n[ref]: /url", Link.class, SourceSpan.of(0, 0, 11)); - assertInlineSpans("[text][ref]\n\n[ref]: /url", Text.class, SourceSpan.of(0, 1, 4)); - assertInlineSpans("[notalink]", Text.class, SourceSpan.of(0, 0, 10)); + assertInlineSpans("\n[text]\n\n[text]: /url", Link.class, SourceSpan.of(1, 0, 1, 6)); + assertInlineSpans("\n[text]\n\n[text]: /url", Text.class, SourceSpan.of(1, 1, 2, 4)); + assertInlineSpans("\n[text][]\n\n[text]: /url", Link.class, SourceSpan.of(1, 0, 1, 8)); + assertInlineSpans("\n[text][]\n\n[text]: /url", Text.class, SourceSpan.of(1, 1, 2, 4)); + assertInlineSpans("\n[text][ref]\n\n[ref]: /url", Link.class, SourceSpan.of(1, 0, 1, 11)); + assertInlineSpans("\n[text][ref]\n\n[ref]: /url", Text.class, SourceSpan.of(1, 1, 2, 4)); + assertInlineSpans("\n[notalink]", Text.class, SourceSpan.of(1, 0, 1, 10)); } @Test public void inlineEmphasis() { - assertInlineSpans("*hey*", Emphasis.class, SourceSpan.of(0, 0, 5)); - assertInlineSpans("*hey*", Text.class, SourceSpan.of(0, 1, 3)); - assertInlineSpans("**hey**", StrongEmphasis.class, SourceSpan.of(0, 0, 7)); - assertInlineSpans("**hey**", Text.class, SourceSpan.of(0, 2, 3)); + assertInlineSpans("\n*hey*", Emphasis.class, SourceSpan.of(1, 0, 1, 5)); + assertInlineSpans("\n*hey*", Text.class, SourceSpan.of(1, 1, 2, 3)); + assertInlineSpans("\n**hey**", StrongEmphasis.class, SourceSpan.of(1, 0, 1, 7)); + assertInlineSpans("\n**hey**", Text.class, SourceSpan.of(1, 2, 3, 3)); // This is an interesting one. It renders like this: //

    *hey

    // The delimiter processor only uses one of the asterisks. // So the first Text node should be the `*` at the beginning with the correct span. - assertInlineSpans("**hey*", Text.class, SourceSpan.of(0, 0, 1)); - assertInlineSpans("**hey*", Emphasis.class, SourceSpan.of(0, 1, 5)); + assertInlineSpans("\n**hey*", Text.class, SourceSpan.of(1, 0, 1, 1)); + assertInlineSpans("\n**hey*", Emphasis.class, SourceSpan.of(1, 1, 2, 5)); - assertInlineSpans("***hey**", Text.class, SourceSpan.of(0, 0, 1)); - assertInlineSpans("***hey**", StrongEmphasis.class, SourceSpan.of(0, 1, 7)); + assertInlineSpans("\n***hey**", Text.class, SourceSpan.of(1, 0, 1, 1)); + assertInlineSpans("\n***hey**", StrongEmphasis.class, SourceSpan.of(1, 1, 2, 7)); Node document = INLINES_PARSER.parse("*hey**"); Node lastText = document.getFirstChild().getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 5, 1)), lastText.getSourceSpans()); + assertEquals(List.of(SourceSpan.of(0, 5, 5, 1)), lastText.getSourceSpans()); } @Test public void tabExpansion() { - assertInlineSpans(">\tfoo", BlockQuote.class, SourceSpan.of(0, 0, 5)); - assertInlineSpans(">\tfoo", Text.class, SourceSpan.of(0, 2, 3)); + assertInlineSpans(">\tfoo", BlockQuote.class, SourceSpan.of(0, 0, 0, 5)); + assertInlineSpans(">\tfoo", Text.class, SourceSpan.of(0, 2, 2, 3)); - assertInlineSpans("a\tb", Text.class, SourceSpan.of(0, 0, 3)); + assertInlineSpans("a\tb", Text.class, SourceSpan.of(0, 0, 0, 3)); } - private String visualizeSourceSpans(String source) { - Node document = PARSER.parse(source); - return SourceSpanRenderer.render(document, source); + @Test + public void differentLineTerminators() { + var input = "foo\nbar\rbaz\r\nqux\r\n\r\n> *hi*"; + assertSpans(input, Paragraph.class, + SourceSpan.of(0, 0, 0, 3), + SourceSpan.of(1, 0, 4, 3), + SourceSpan.of(2, 0, 8, 3), + SourceSpan.of(3, 0, 13, 3)); + assertSpans(input, BlockQuote.class, + SourceSpan.of(5, 0, 20, 6)); + + assertInlineSpans(input, Emphasis.class, SourceSpan.of(5, 2, 22, 4)); + } + + private void assertVisualize(String source, String expected) { + var doc = PARSER.parse(source); + assertEquals(expected, SourceSpanRenderer.renderWithLineColumn(doc, source)); + assertEquals(expected, SourceSpanRenderer.renderWithInputIndex(doc, source)); } private static void assertSpans(String input, Class nodeClass, SourceSpan... expectedSourceSpans) { assertSpans(PARSER.parse(input), nodeClass, expectedSourceSpans); + try { + assertSpans(PARSER.parseReader(new StringReader(input)), nodeClass, expectedSourceSpans); + } catch (IOException e) { + throw new RuntimeException(e); + } } private static void assertInlineSpans(String input, Class nodeClass, SourceSpan... expectedSourceSpans) { assertSpans(INLINES_PARSER.parse(input), nodeClass, expectedSourceSpans); + try { + assertSpans(INLINES_PARSER.parseReader(new StringReader(input)), nodeClass, expectedSourceSpans); + } catch (IOException e) { + throw new RuntimeException(e); + } } private static void assertSpans(Node rootNode, Class nodeClass, SourceSpan... expectedSourceSpans) { From 2e52d5a64447ca99b53ee807d08b1fe74bef175e Mon Sep 17 00:00:00 2001 From: mykolagolubyev Date: Sat, 19 Oct 2024 17:42:37 -0400 Subject: [PATCH 171/345] add znai as user --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7ab691e6c..717db7c9f 100644 --- a/README.md +++ b/README.md @@ -423,6 +423,7 @@ Some users of this library (feel free to raise a PR if you want to be added): * Atlassian (where the library was initially developed) * Java (OpenJDK), see [here](https://github.com/openjdk/jdk/blob/3895b8fc0b2c6d187080dba6fe08297adad4a480/src/jdk.internal.md/share/classes/module-info.java) * Gitiles/Gerrit, see [here](https://gerrit-review.googlesource.com/c/gitiles/+/353794) +* Znai, see [here](https://github.com/testingisdocumenting/znai) See also -------- From e0090314f2c6a1b8cb3e4dbcea78eecd254d16a5 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 21 Oct 2024 22:10:01 +1100 Subject: [PATCH 172/345] Add license to core pom, use SPDX identifier --- commonmark/pom.xml | 8 ++++++++ pom.xml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 3d4ddc2b7..4495fd131 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -54,4 +54,12 @@ + + + BSD-2-Clause + https://opensource.org/licenses/BSD-2-Clause + repo + + + diff --git a/pom.xml b/pom.xml index dc1aa344d..d8b597c27 100644 --- a/pom.xml +++ b/pom.xml @@ -263,7 +263,7 @@ - BSD 2-Clause License + BSD-2-Clause https://opensource.org/licenses/BSD-2-Clause repo From c5c95477c9297310ce641f1f7f41ecc4facd4351 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 21 Oct 2024 22:37:38 +1100 Subject: [PATCH 173/345] README: Add source positions section --- README.md | 25 +++++++++++++++++++ .../org/commonmark/test/UsageExampleTest.java | 16 ++++++++++++ 2 files changed, 41 insertions(+) diff --git a/README.md b/README.md index 717db7c9f..d803f32d7 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,31 @@ class WordCountVisitor extends AbstractVisitor { } ``` +#### Source positions + +If you want to know where a parsed `Node` appeared in the input source text, +you can request the parser to return source positions like this: + +```java +var parser = Parser.builder().includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES).build(); +``` + +Then parse nodes and inspect source positions: + +```java +var source = "foo\n\nbar *baz*"; +var doc = parser.parse(source); +var emphasis = doc.getLastChild().getLastChild(); +var s = emphasis.getSourceSpans().get(0); +s.getLineIndex(); // 2 (third line) +s.getColumnIndex(); // 4 (fifth column) +s.getInputIndex(); // 9 (string index 9) +s.getLength(); // 5 +source.substring(s.getInputIndex(), s.getInputIndex() + s.getLength()); // "*baz*" +``` + +If you're only interested in blocks and not inlines, use `IncludeSourceSpans.BLOCKS`. + #### Add or change attributes of HTML elements Sometimes you might want to customize how HTML is rendered. If all you diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 1ffb7b64a..1bff79a26 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -1,6 +1,7 @@ package org.commonmark.test; import org.commonmark.node.*; +import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.*; @@ -58,6 +59,21 @@ public void visitor() { assertEquals(4, visitor.wordCount); } + @Test + public void sourcePositions() { + var parser = Parser.builder().includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES).build(); + + var source = "foo\n\nbar *baz*"; + var doc = parser.parse(source); + var emphasis = doc.getLastChild().getLastChild(); + var s = emphasis.getSourceSpans().get(0); + assertEquals(2, s.getLineIndex()); + assertEquals(4, s.getColumnIndex()); + assertEquals(9, s.getInputIndex()); + assertEquals(5, s.getLength()); + assertEquals("*baz*", source.substring(s.getInputIndex(), s.getInputIndex() + s.getLength())); + } + @Test public void addAttributes() { Parser parser = Parser.builder().build(); From 8a379e2e60f52a2a114189766df22fa2051bd432 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 21 Oct 2024 22:41:40 +1100 Subject: [PATCH 174/345] Test on Java 23 as well --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b49fb8164..24160d342 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [11, 17, 21] + java: [11, 17, 21, 23] steps: - name: Checkout sources uses: actions/checkout@v4 From 5e6a5cc8f134674b3f29ecf58f18e8a41edeeba0 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 21 Oct 2024 23:01:16 +1100 Subject: [PATCH 175/345] Prepare CHANGELOG for release 0.24 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f98fe97d4..fb412397f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## [0.24.0] - 2024-10-21 +### Added +- `SourceSpan` on nodes now have a `getInputIndex` to get the index within the + original input string (in addition to the existing line/column indexes). + This is useful when looking up the input source: It can now be done using + `substring` instead of having to split the input into lines first (#348) +- Configurable line break rendering for `TextContentRenderer` via `lineBreakRendering` + on the builder; e.g. `LineBreakRendering.SEPARATE_BLOCKS` will render an empty + line between blocks (#344) +### Changed +- Adopted small changes from OpenJDK vendoring to make updates easier for them (#343) +### Fixed +- Enable overriding of built-in node rendering for `TextContentRenderer` (#346) + ## [0.23.0] - 2024-09-16 ### Added - New extension for footnotes! @@ -445,6 +459,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.24.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.23.0...commonmark-parent-0.24.0 [0.23.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.22.0...commonmark-parent-0.23.0 [0.22.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.21.0...commonmark-parent-0.22.0 [0.21.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.20.0...commonmark-parent-0.21.0 From ea85a7ddc699ab27772b2ce2f612f972695d771d Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 21 Oct 2024 23:02:57 +1100 Subject: [PATCH 176/345] Prepare for version 0.24.0 Ran `mvn versions:set -DnewVersion=0.24.0-SNAPSHOT` --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 22 +++++++++++----------- 13 files changed, 23 insertions(+), 23 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 9e879640f..9271f9f80 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 36204b775..13f14af0d 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 40c234e88..fbb8a109f 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 06e828240..f12f71482 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 8998cb4c1..96b9055b6 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index c706a1fe6..8b8d4f158 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 277dad07c..bee561579 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 1a42059dc..52a86a802 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 079aaf46c..97da86bb2 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index e82661ed4..4f6843e10 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index a8c8c73ad..795b1a1ce 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 4495fd131..4c2cb19d7 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index d8b597c27..9e1cd25c3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ org.commonmark commonmark - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT org.commonmark commonmark-ext-ins - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT org.commonmark commonmark-test-util - 0.23.1-SNAPSHOT + 0.24.0-SNAPSHOT From 9a395abec74f96b48cfd45f681507b72dc31d9c4 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:08:16 +0000 Subject: [PATCH 177/345] [maven-release-plugin] prepare release commonmark-parent-0.24.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 24 ++++++++++++------------ 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 9271f9f80..6ae814adc 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0-SNAPSHOT + 0.24.0 commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 13f14af0d..a8a8ca58c 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0-SNAPSHOT + 0.24.0 commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index fbb8a109f..24a842518 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0-SNAPSHOT + 0.24.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index f12f71482..bfb2bcdb2 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0-SNAPSHOT + 0.24.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 96b9055b6..0dc44d39f 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0-SNAPSHOT + 0.24.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 8b8d4f158..a9c49d129 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0-SNAPSHOT + 0.24.0 commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index bee561579..d8f415f7f 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0-SNAPSHOT + 0.24.0 commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 52a86a802..c74f8e176 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0-SNAPSHOT + 0.24.0 commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 97da86bb2..078b6311d 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.24.0-SNAPSHOT + 0.24.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 4f6843e10..95686039e 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0-SNAPSHOT + 0.24.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 795b1a1ce..8fc04a29d 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0-SNAPSHOT + 0.24.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 4c2cb19d7..a47ac0069 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0-SNAPSHOT + 0.24.0 commonmark diff --git a/pom.xml b/pom.xml index 9e1cd25c3..912915ab0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.24.0-SNAPSHOT + 0.24.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ org.commonmark commonmark - 0.24.0-SNAPSHOT + 0.24.0 org.commonmark commonmark-ext-autolink - 0.24.0-SNAPSHOT + 0.24.0 org.commonmark commonmark-ext-image-attributes - 0.24.0-SNAPSHOT + 0.24.0 org.commonmark commonmark-ext-ins - 0.24.0-SNAPSHOT + 0.24.0 org.commonmark commonmark-ext-gfm-strikethrough - 0.24.0-SNAPSHOT + 0.24.0 org.commonmark commonmark-ext-gfm-tables - 0.24.0-SNAPSHOT + 0.24.0 org.commonmark commonmark-ext-heading-anchor - 0.24.0-SNAPSHOT + 0.24.0 org.commonmark commonmark-ext-task-list-items - 0.24.0-SNAPSHOT + 0.24.0 org.commonmark commonmark-ext-yaml-front-matter - 0.24.0-SNAPSHOT + 0.24.0 org.commonmark commonmark-test-util - 0.24.0-SNAPSHOT + 0.24.0 @@ -279,7 +279,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - HEAD + commonmark-parent-0.24.0 From 0659cce8400c0590d2f51d56cd27ca802193153d Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:08:18 +0000 Subject: [PATCH 178/345] [maven-release-plugin] prepare for next development iteration --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 24 ++++++++++++------------ 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 6ae814adc..c2f9bf438 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0 + 0.24.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index a8a8ca58c..d34e4e2f9 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0 + 0.24.1-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 24a842518..1fd1269bf 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0 + 0.24.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index bfb2bcdb2..d261a1d24 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0 + 0.24.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 0dc44d39f..e4a62b2b3 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0 + 0.24.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index a9c49d129..55b0dce20 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0 + 0.24.1-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index d8f415f7f..9dbe2cf06 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0 + 0.24.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index c74f8e176..3926c8b5e 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0 + 0.24.1-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 078b6311d..9e329e0d3 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.24.0 + 0.24.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 95686039e..c5e572291 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0 + 0.24.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 8fc04a29d..7fb4881f1 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0 + 0.24.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index a47ac0069..17f3bc747 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.0 + 0.24.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 912915ab0..031869f6e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.24.0 + 0.24.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ org.commonmark commonmark - 0.24.0 + 0.24.1-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.24.0 + 0.24.1-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.24.0 + 0.24.1-SNAPSHOT org.commonmark commonmark-ext-ins - 0.24.0 + 0.24.1-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.24.0 + 0.24.1-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.24.0 + 0.24.1-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.24.0 + 0.24.1-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.24.0 + 0.24.1-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.24.0 + 0.24.1-SNAPSHOT org.commonmark commonmark-test-util - 0.24.0 + 0.24.1-SNAPSHOT @@ -279,7 +279,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - commonmark-parent-0.24.0 + HEAD From f3d5ff39de4f0f4cbae70547642ccf71684d38ec Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 21 Oct 2024 23:23:03 +1100 Subject: [PATCH 179/345] README: Bump version to 0.24.0 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d803f32d7..0bcd95f44 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.23.0 + 0.24.0 ``` @@ -291,7 +291,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.23.0 + 0.24.0 ``` From 7bbaa9a7578fc8fbec1f6524a2ee7f73dbcb6e9c Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Oct 2024 22:06:38 +1100 Subject: [PATCH 180/345] README: Used by --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0bcd95f44..dca939714 100644 --- a/README.md +++ b/README.md @@ -445,10 +445,11 @@ Used by ------- Some users of this library (feel free to raise a PR if you want to be added): -* Atlassian (where the library was initially developed) -* Java (OpenJDK), see [here](https://github.com/openjdk/jdk/blob/3895b8fc0b2c6d187080dba6fe08297adad4a480/src/jdk.internal.md/share/classes/module-info.java) -* Gitiles/Gerrit, see [here](https://gerrit-review.googlesource.com/c/gitiles/+/353794) -* Znai, see [here](https://github.com/testingisdocumenting/znai) +* [Atlassian](https://www.atlassian.com/) (where the library was initially developed) +* Java (OpenJDK) ([link](https://github.com/openjdk/jdk/blob/3895b8fc0b2c6d187080dba6fe08297adad4a480/src/jdk.internal.md/share/classes/module-info.java)) +* [Gerrit](https://www.gerritcodereview.com/) code review/Gitiles ([link](https://gerrit-review.googlesource.com/c/gitiles/+/353794)) +* [Clerk](https://clerk.vision/) moldable live programming for Clojure +* [Znai](https://github.com/testingisdocumenting/znai) See also -------- From cd649a1657bcd844c66a33c4af007a6f1926f8c2 Mon Sep 17 00:00:00 2001 From: sebthom Date: Sun, 5 Jan 2025 17:26:47 +0100 Subject: [PATCH 181/345] refact: remove unused imports --- .../org/commonmark/ext/footnotes/FootnotesExtension.java | 1 - .../ext/footnotes/internal/FootnoteLinkProcessor.java | 1 - .../ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java | 5 ----- .../src/main/java/org/commonmark/testutil/SpecTestCase.java | 3 --- .../main/java/org/commonmark/internal/DocumentParser.java | 1 - .../commonmark/internal/LinkReferenceDefinitionParser.java | 1 - .../main/java/org/commonmark/internal/ParagraphParser.java | 1 - .../src/main/java/org/commonmark/internal/util/Escaping.java | 1 - .../commonmark/renderer/markdown/MarkdownRendererTest.java | 1 - .../src/test/java/org/commonmark/test/HtmlRendererTest.java | 2 -- .../test/java/org/commonmark/test/ListBlockParserTest.java | 1 - .../test/java/org/commonmark/test/SourceSpanRenderer.java | 1 - .../java/org/commonmark/test/TextContentRendererTest.java | 2 -- .../java/org/commonmark/test/ThematicBreakParserTest.java | 1 - 14 files changed, 22 deletions(-) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java index e2f90c239..dd532fa34 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java @@ -3,7 +3,6 @@ import org.commonmark.Extension; import org.commonmark.ext.footnotes.internal.*; import org.commonmark.parser.Parser; -import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java index b2b420bae..07b008576 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java @@ -4,7 +4,6 @@ import org.commonmark.ext.footnotes.FootnoteReference; import org.commonmark.ext.footnotes.InlineFootnote; import org.commonmark.node.LinkReferenceDefinition; -import org.commonmark.node.Text; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.beta.LinkInfo; import org.commonmark.parser.beta.LinkProcessor; diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java index 342fb7ebb..eddf6f2ea 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java @@ -5,14 +5,9 @@ import org.commonmark.ext.footnotes.InlineFootnote; import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlWriter; import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; import org.commonmark.renderer.markdown.MarkdownWriter; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Set; public class FootnoteMarkdownNodeRenderer implements NodeRenderer { diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java index 3be768682..2fb175772 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java @@ -2,7 +2,6 @@ import org.commonmark.testutil.example.Example; import org.commonmark.testutil.example.ExampleReader; -import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -10,8 +9,6 @@ import java.util.ArrayList; import java.util.List; -import static org.commonmark.testutil.Asserts.assertRendering; - @RunWith(Parameterized.class) public abstract class SpecTestCase { diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 7fd2d4300..6059cc51c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -13,7 +13,6 @@ import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.text.Characters; -import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.util.*; diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index 070f29ceb..b58e669ef 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -2,7 +2,6 @@ import org.commonmark.internal.util.Escaping; import org.commonmark.internal.util.LinkScanner; -import org.commonmark.node.DefinitionMap; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.node.SourceSpan; import org.commonmark.parser.SourceLine; diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index 18808d499..93a2dd593 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -9,7 +9,6 @@ import org.commonmark.parser.block.ParserState; import java.util.List; -import java.util.Map; public class ParagraphParser extends AbstractBlockParser { diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java index d928b6f43..3350003c0 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java @@ -1,6 +1,5 @@ package org.commonmark.internal.util; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.regex.Matcher; diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 59d525fed..c47a6dde0 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -2,7 +2,6 @@ import org.commonmark.node.*; import org.commonmark.parser.Parser; -import org.commonmark.testutil.Asserts; import org.junit.Test; import static org.commonmark.testutil.Asserts.assertRendering; diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 018b9e453..90a4aff62 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -4,8 +4,6 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.*; -import org.commonmark.testutil.Asserts; -import org.commonmark.testutil.RenderingTestCase; import org.commonmark.testutil.TestResources; import org.junit.Test; diff --git a/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java b/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java index 667d60efe..4a38bc412 100644 --- a/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java @@ -6,7 +6,6 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; public class ListBlockParserTest { diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java b/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java index 5181a36e7..c29aac61e 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java @@ -2,7 +2,6 @@ import org.commonmark.node.AbstractVisitor; import org.commonmark.node.Node; -import org.commonmark.node.SourceSpan; import java.util.*; diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index 45d7aa91d..fd7744eff 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -13,8 +13,6 @@ import java.util.Set; -import static org.junit.Assert.assertEquals; - public class TextContentRendererTest { private static final Parser PARSER = Parser.builder().build(); diff --git a/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java b/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java index a6b297f8e..2b15f8add 100644 --- a/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java @@ -5,7 +5,6 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; public class ThematicBreakParserTest { From a689cdd289e42cbc9a776d321a1ed1e3fe16e610 Mon Sep 17 00:00:00 2001 From: sebthom Date: Sun, 5 Jan 2025 17:26:57 +0100 Subject: [PATCH 182/345] refact: remove redundant superinterface/type argument --- .../ext/gfm/tables/internal/TableMarkdownNodeRenderer.java | 3 +-- .../org/commonmark/renderer/markdown/MarkdownRenderer.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java index 2fe60f80d..b0705f579 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java @@ -2,7 +2,6 @@ import org.commonmark.ext.gfm.tables.*; import org.commonmark.node.Node; -import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; import org.commonmark.renderer.markdown.MarkdownWriter; import org.commonmark.text.AsciiMatcher; @@ -13,7 +12,7 @@ /** * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to text content. */ -public class TableMarkdownNodeRenderer extends TableNodeRenderer implements NodeRenderer { +public class TableMarkdownNodeRenderer extends TableNodeRenderer { private final MarkdownWriter writer; private final MarkdownNodeRendererContext context; diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java index 7683b6921..0f8559f32 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java @@ -130,7 +130,7 @@ private class RendererContext implements MarkdownNodeRendererContext { private RendererContext(MarkdownWriter writer) { // Set fields that are used by interface this.writer = writer; - Set escapes = new HashSet(); + Set escapes = new HashSet<>(); for (MarkdownNodeRendererFactory factory : nodeRendererFactories) { escapes.addAll(factory.getSpecialCharacters()); } From f6a6a3eda21bb8bd9919ed9064153200eb7334dc Mon Sep 17 00:00:00 2001 From: sebthom Date: Sun, 5 Jan 2025 17:30:30 +0100 Subject: [PATCH 183/345] refact: remove unused local variable --- .../test/java/org/commonmark/test/TextContentRendererTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index fd7744eff..bbacf3da4 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -45,7 +45,6 @@ public void textContentHeading() { @Test public void textContentEmphasis() { String s; - String rendered; s = "***foo***"; assertCompact(s, "foo"); From a45d3d24f19cbf2eb94da3a6cbd47f52deec66b6 Mon Sep 17 00:00:00 2001 From: sebthom Date: Sun, 5 Jan 2025 17:36:59 +0100 Subject: [PATCH 184/345] refact: fix deprecation warnings --- .../test/java/org/commonmark/ext/gfm/tables/TablesTest.java | 2 +- .../test/java/org/commonmark/internal/DocumentParserTest.java | 2 +- .../src/test/java/org/commonmark/test/HtmlRendererTest.java | 2 +- .../org/commonmark/test/LinkReferenceDefinitionNodeTest.java | 2 +- commonmark/src/test/java/org/commonmark/test/ParserTest.java | 2 +- .../test/java/org/commonmark/test/TextContentRendererTest.java | 3 ++- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java index 415a17815..57f4a4ae5 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -15,8 +15,8 @@ import java.util.*; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; public class TablesTest extends RenderingTestCase { diff --git a/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java b/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java index 621bef25b..45143b852 100644 --- a/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java @@ -9,7 +9,7 @@ import java.util.Set; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertTrue; public class DocumentParserTest { diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 90a4aff62..8e5a8f30d 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -17,8 +17,8 @@ import java.util.concurrent.Future; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; public class HtmlRendererTest { diff --git a/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java b/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java index bf7bde6ec..81a71ee69 100644 --- a/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java +++ b/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java @@ -8,7 +8,7 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; public class LinkReferenceDefinitionNodeTest { diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index f56cd560c..447a50c3d 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -21,8 +21,8 @@ import java.util.concurrent.Future; import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; public class ParserTest { diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index bbacf3da4..93ca87d93 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -19,7 +19,8 @@ public class TextContentRendererTest { private static final TextContentRenderer COMPACT_RENDERER = TextContentRenderer.builder().build(); private static final TextContentRenderer SEPARATE_RENDERER = TextContentRenderer.builder() .lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS).build(); - private static final TextContentRenderer STRIPPED_RENDERER = TextContentRenderer.builder().stripNewlines(true).build(); + private static final TextContentRenderer STRIPPED_RENDERER = TextContentRenderer.builder() + .lineBreakRendering(LineBreakRendering.STRIP).build(); @Test public void textContentText() { From 5cd425e471c328ff0625a85738fe3f3f84ab19d6 Mon Sep 17 00:00:00 2001 From: sebthom Date: Sun, 5 Jan 2025 17:48:55 +0100 Subject: [PATCH 185/345] refact: add missing Override annotations --- .../ext/gfm/tables/internal/TableHtmlNodeRenderer.java | 5 +++++ .../gfm/tables/internal/TableTextContentNodeRenderer.java | 5 +++++ .../commonmark/integration/ExtensionsIntegrationTest.java | 1 + .../main/java/org/commonmark/internal/BlockQuoteParser.java | 1 + commonmark/src/main/java/org/commonmark/node/Block.java | 1 + .../src/test/java/org/commonmark/test/UsageExampleTest.java | 2 ++ 6 files changed, 15 insertions(+) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java index fd07e84df..966c4c151 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java @@ -17,6 +17,7 @@ public TableHtmlNodeRenderer(HtmlNodeRendererContext context) { this.context = context; } + @Override protected void renderBlock(TableBlock tableBlock) { htmlWriter.line(); htmlWriter.tag("table", getAttributes(tableBlock, "table")); @@ -25,6 +26,7 @@ protected void renderBlock(TableBlock tableBlock) { htmlWriter.line(); } + @Override protected void renderHead(TableHead tableHead) { htmlWriter.line(); htmlWriter.tag("thead", getAttributes(tableHead, "thead")); @@ -33,6 +35,7 @@ protected void renderHead(TableHead tableHead) { htmlWriter.line(); } + @Override protected void renderBody(TableBody tableBody) { htmlWriter.line(); htmlWriter.tag("tbody", getAttributes(tableBody, "tbody")); @@ -41,6 +44,7 @@ protected void renderBody(TableBody tableBody) { htmlWriter.line(); } + @Override protected void renderRow(TableRow tableRow) { htmlWriter.line(); htmlWriter.tag("tr", getAttributes(tableRow, "tr")); @@ -49,6 +53,7 @@ protected void renderRow(TableRow tableRow) { htmlWriter.line(); } + @Override protected void renderCell(TableCell tableCell) { String tagName = tableCell.isHeader() ? "th" : "td"; htmlWriter.line(); diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java index 7d28f61a8..0ba6894b5 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java @@ -22,6 +22,7 @@ public TableTextContentNodeRenderer(TextContentNodeRendererContext context) { this.context = context; } + @Override protected void renderBlock(TableBlock tableBlock) { // Render rows tight textContentWriter.pushTight(true); @@ -30,19 +31,23 @@ protected void renderBlock(TableBlock tableBlock) { textContentWriter.block(); } + @Override protected void renderHead(TableHead tableHead) { renderChildren(tableHead); } + @Override protected void renderBody(TableBody tableBody) { renderChildren(tableBody); } + @Override protected void renderRow(TableRow tableRow) { renderChildren(tableRow); textContentWriter.block(); } + @Override protected void renderCell(TableCell tableCell) { renderChildren(tableCell); // For the last cell in row, don't render the delimiter diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java index f5d84b5a9..64cf60ed9 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java @@ -31,6 +31,7 @@ public void testTaskListItems() { } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); } diff --git a/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java b/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java index 87c923e06..572c491f8 100644 --- a/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java @@ -46,6 +46,7 @@ private static boolean isMarker(ParserState state, int index) { } public static class Factory extends AbstractBlockParserFactory { + @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { int nextNonSpace = state.getNextNonSpaceIndex(); if (isMarker(state, nextNonSpace)) { diff --git a/commonmark/src/main/java/org/commonmark/node/Block.java b/commonmark/src/main/java/org/commonmark/node/Block.java index 753447c5c..332346b0e 100644 --- a/commonmark/src/main/java/org/commonmark/node/Block.java +++ b/commonmark/src/main/java/org/commonmark/node/Block.java @@ -5,6 +5,7 @@ */ public abstract class Block extends Node { + @Override public Block getParent() { return (Block) super.getParent(); } diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 1bff79a26..63901a49b 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -79,6 +79,7 @@ public void addAttributes() { Parser parser = Parser.builder().build(); HtmlRenderer renderer = HtmlRenderer.builder() .attributeProviderFactory(new AttributeProviderFactory() { + @Override public AttributeProvider create(AttributeProviderContext context) { return new ImageAttributeProvider(); } @@ -95,6 +96,7 @@ public void customizeRendering() { Parser parser = Parser.builder().build(); HtmlRenderer renderer = HtmlRenderer.builder() .nodeRendererFactory(new HtmlNodeRendererFactory() { + @Override public NodeRenderer create(HtmlNodeRendererContext context) { return new IndentedCodeBlockNodeRenderer(context); } From 2e24c8670f7b4ade7a8bb234fa117a9332b9225e Mon Sep 17 00:00:00 2001 From: sebthom Date: Sun, 5 Jan 2025 18:08:12 +0100 Subject: [PATCH 186/345] fix: Add 'requires transitive' to fix compiler warning This fixes the compiler warning "The type Extension from module org.commonmark may not be accessible to clients due to missing 'requires transitive'" --- commonmark-ext-autolink/src/main/java/module-info.java | 2 +- commonmark-ext-footnotes/src/main/java/module-info.java | 2 +- commonmark-ext-gfm-strikethrough/src/main/java/module-info.java | 2 +- commonmark-ext-gfm-tables/src/main/java/module-info.java | 2 +- commonmark-ext-heading-anchor/src/main/java/module-info.java | 2 +- commonmark-ext-image-attributes/src/main/java/module-info.java | 2 +- commonmark-ext-ins/src/main/java/module-info.java | 2 +- commonmark-ext-task-list-items/src/main/java/module-info.java | 2 +- commonmark-ext-yaml-front-matter/src/main/java/module-info.java | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/commonmark-ext-autolink/src/main/java/module-info.java b/commonmark-ext-autolink/src/main/java/module-info.java index 6fe98c3b1..561934b85 100644 --- a/commonmark-ext-autolink/src/main/java/module-info.java +++ b/commonmark-ext-autolink/src/main/java/module-info.java @@ -1,6 +1,6 @@ module org.commonmark.ext.autolink { exports org.commonmark.ext.autolink; - requires org.commonmark; + requires transitive org.commonmark; requires org.nibor.autolink; } diff --git a/commonmark-ext-footnotes/src/main/java/module-info.java b/commonmark-ext-footnotes/src/main/java/module-info.java index 806d65c3e..0667b2801 100644 --- a/commonmark-ext-footnotes/src/main/java/module-info.java +++ b/commonmark-ext-footnotes/src/main/java/module-info.java @@ -1,5 +1,5 @@ module org.commonmark.ext.footnotes { exports org.commonmark.ext.footnotes; - requires org.commonmark; + requires transitive org.commonmark; } diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/module-info.java b/commonmark-ext-gfm-strikethrough/src/main/java/module-info.java index 772710f00..b6204934b 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/module-info.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/module-info.java @@ -1,5 +1,5 @@ module org.commonmark.ext.gfm.strikethrough { exports org.commonmark.ext.gfm.strikethrough; - requires org.commonmark; + requires transitive org.commonmark; } diff --git a/commonmark-ext-gfm-tables/src/main/java/module-info.java b/commonmark-ext-gfm-tables/src/main/java/module-info.java index 472c84c3d..7e6d2629c 100644 --- a/commonmark-ext-gfm-tables/src/main/java/module-info.java +++ b/commonmark-ext-gfm-tables/src/main/java/module-info.java @@ -1,5 +1,5 @@ module org.commonmark.ext.gfm.tables { exports org.commonmark.ext.gfm.tables; - requires org.commonmark; + requires transitive org.commonmark; } diff --git a/commonmark-ext-heading-anchor/src/main/java/module-info.java b/commonmark-ext-heading-anchor/src/main/java/module-info.java index 3b94c75ec..2369323a6 100644 --- a/commonmark-ext-heading-anchor/src/main/java/module-info.java +++ b/commonmark-ext-heading-anchor/src/main/java/module-info.java @@ -1,5 +1,5 @@ module org.commonmark.ext.heading.anchor { exports org.commonmark.ext.heading.anchor; - requires org.commonmark; + requires transitive org.commonmark; } diff --git a/commonmark-ext-image-attributes/src/main/java/module-info.java b/commonmark-ext-image-attributes/src/main/java/module-info.java index 171281091..42d04a358 100644 --- a/commonmark-ext-image-attributes/src/main/java/module-info.java +++ b/commonmark-ext-image-attributes/src/main/java/module-info.java @@ -1,5 +1,5 @@ module org.commonmark.ext.image.attributes { exports org.commonmark.ext.image.attributes; - requires org.commonmark; + requires transitive org.commonmark; } diff --git a/commonmark-ext-ins/src/main/java/module-info.java b/commonmark-ext-ins/src/main/java/module-info.java index aa5c5e84c..fb96ea598 100644 --- a/commonmark-ext-ins/src/main/java/module-info.java +++ b/commonmark-ext-ins/src/main/java/module-info.java @@ -1,5 +1,5 @@ module org.commonmark.ext.ins { exports org.commonmark.ext.ins; - requires org.commonmark; + requires transitive org.commonmark; } diff --git a/commonmark-ext-task-list-items/src/main/java/module-info.java b/commonmark-ext-task-list-items/src/main/java/module-info.java index 30134c51b..9528323ea 100644 --- a/commonmark-ext-task-list-items/src/main/java/module-info.java +++ b/commonmark-ext-task-list-items/src/main/java/module-info.java @@ -1,5 +1,5 @@ module org.commonmark.ext.task.list.items { exports org.commonmark.ext.task.list.items; - requires org.commonmark; + requires transitive org.commonmark; } diff --git a/commonmark-ext-yaml-front-matter/src/main/java/module-info.java b/commonmark-ext-yaml-front-matter/src/main/java/module-info.java index 20d38fe0a..5f96c14ad 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/module-info.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/module-info.java @@ -1,5 +1,5 @@ module org.commonmark.ext.front.matter { exports org.commonmark.ext.front.matter; - requires org.commonmark; + requires transitive org.commonmark; } From 504f43c22da03fa09bab6f4b967dff9a5f9d1b6e Mon Sep 17 00:00:00 2001 From: Yang Dai <107718193+YangDai2003@users.noreply.github.com> Date: Sat, 8 Feb 2025 17:08:25 +0100 Subject: [PATCH 187/345] Update README.md Add Open Note to the "used by" list. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dca939714..3bcbaffb9 100644 --- a/README.md +++ b/README.md @@ -450,6 +450,7 @@ Some users of this library (feel free to raise a PR if you want to be added): * [Gerrit](https://www.gerritcodereview.com/) code review/Gitiles ([link](https://gerrit-review.googlesource.com/c/gitiles/+/353794)) * [Clerk](https://clerk.vision/) moldable live programming for Clojure * [Znai](https://github.com/testingisdocumenting/znai) +* [Open Note](https://github.com/YangDai2003/OpenNote-Compose) a markdown editor and note-taking app for Android See also -------- From 762c2adba503e04947fd5c378de19484e13ac24e Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Thu, 13 Feb 2025 14:04:18 +0100 Subject: [PATCH 188/345] Add Quarkus Roq in "Used by" --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dca939714..b86c782cf 100644 --- a/README.md +++ b/README.md @@ -450,6 +450,7 @@ Some users of this library (feel free to raise a PR if you want to be added): * [Gerrit](https://www.gerritcodereview.com/) code review/Gitiles ([link](https://gerrit-review.googlesource.com/c/gitiles/+/353794)) * [Clerk](https://clerk.vision/) moldable live programming for Clojure * [Znai](https://github.com/testingisdocumenting/znai) +* [Quarkus Roq](https://github.com/quarkiverse/quarkus-roq/) The Roq Static Site Generator allows to easily create a static website or blog using Quarkus super-powers. See also -------- From 7db535466a86298366d5657883f14413e7d2b15b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 23 Mar 2025 22:23:43 +1100 Subject: [PATCH 189/345] MarkdownRenderer: Fix overriding of core rendering --- CHANGELOG.md | 8 +++++ .../internal/renderer/NodeRendererMap.java | 3 ++ .../renderer/markdown/MarkdownRenderer.java | 8 ++--- .../markdown/MarkdownRendererTest.java | 35 +++++++++++++++++++ 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb412397f..62831d7b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## [Unreleased] +### Added +### Changed +### Fixed +- `MarkdownRenderer`: Fix precedence for `nodeRendererFactory`: Factories passed + to the builder can now override rendering for core node types. + ## [0.24.0] - 2024-10-21 ### Added - `SourceSpan` on nodes now have a `getInputIndex` to get the index within the @@ -459,6 +466,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[Unreleased]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.24.0...main [0.24.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.23.0...commonmark-parent-0.24.0 [0.23.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.22.0...commonmark-parent-0.23.0 [0.22.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.21.0...commonmark-parent-0.22.0 diff --git a/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java b/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java index 95ac6ce46..c74f90758 100644 --- a/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java +++ b/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java @@ -13,6 +13,9 @@ public class NodeRendererMap { private final List nodeRenderers = new ArrayList<>(); private final Map, NodeRenderer> renderers = new HashMap<>(32); + /** + * Set the renderer for each {@link NodeRenderer#getNodeTypes()}, unless there was already a renderer set (first wins). + */ public void add(NodeRenderer nodeRenderer) { nodeRenderers.add(nodeRenderer); for (var nodeType : nodeRenderer.getNodeTypes()) { diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java index 0f8559f32..e4996fb08 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java @@ -136,12 +136,10 @@ private RendererContext(MarkdownWriter writer) { } additionalTextEscapes = Collections.unmodifiableSet(escapes); - // The first node renderer for a node type "wins". - for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) { - MarkdownNodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i); + for (var factory : nodeRendererFactories) { // Pass in this as context here, which uses the fields set above - NodeRenderer nodeRenderer = nodeRendererFactory.create(this); - nodeRendererMap.add(nodeRenderer); + var renderer = factory.create(this); + nodeRendererMap.add(renderer); } } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index c47a6dde0..36a5b528c 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -2,8 +2,14 @@ import org.commonmark.node.*; import org.commonmark.parser.Parser; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlNodeRendererFactory; +import org.commonmark.renderer.html.HtmlRenderer; import org.junit.Test; +import java.util.Set; + import static org.commonmark.testutil.Asserts.assertRendering; import static org.junit.Assert.assertEquals; @@ -300,6 +306,35 @@ public void testSoftLineBreaks() { assertRoundTrip("foo\nbar\n"); } + @Test + public void overrideNodeRender() { + var nodeRendererFactory = new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new NodeRenderer() { + @Override + public Set> getNodeTypes() { + return Set.of(Heading.class); + } + + @Override + public void render(Node node) { + context.getWriter().raw("# Custom heading"); + } + }; + } + + @Override + public Set getSpecialCharacters() { + return Set.of(); + } + }; + + MarkdownRenderer renderer = MarkdownRenderer.builder().nodeRendererFactory(nodeRendererFactory).build(); + String rendered = renderer.render(parse("# Hello")); + assertEquals("# Custom heading\n", rendered); + } + private void assertRoundTrip(String input) { String rendered = parseAndRender(input); assertEquals(input, rendered); From 5bb93e7b6fa5b79256f2b23bbb70eb28d12f3926 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 23 Mar 2025 22:49:19 +1100 Subject: [PATCH 190/345] Remove usage of requireNonNullElseGet (Android compat) --- commonmark/src/main/java/org/commonmark/parser/Parser.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 4d534cf72..b98d0581f 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -311,7 +311,11 @@ public Builder inlineParserFactory(InlineParserFactory inlineParserFactory) { } private InlineParserFactory getInlineParserFactory() { - return Objects.requireNonNullElseGet(inlineParserFactory, () -> InlineParserImpl::new); + if (inlineParserFactory != null) { + return inlineParserFactory; + } else { + return InlineParserImpl::new; + } } } From 40f1fd5a0bb9fa7422272848da5d599582add766 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 23 Mar 2025 22:55:23 +1100 Subject: [PATCH 191/345] CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62831d7b7..2a1535e11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ with the exception that 0.x versions can break between minor versions. ### Fixed - `MarkdownRenderer`: Fix precedence for `nodeRendererFactory`: Factories passed to the builder can now override rendering for core node types. +- Fix warning in Eclipse about "missing 'requires transitive'" +- Fix Android incompatibility with `requireNonNullElseGet` ## [0.24.0] - 2024-10-21 ### Added From 245c3459374d57610d9001bd50a508783d23e32e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 23 Mar 2025 23:46:16 +1100 Subject: [PATCH 192/345] Add docs with examples to Node classes --- CHANGELOG.md | 1 + .../main/java/org/commonmark/node/BlockQuote.java | 10 ++++++++++ .../main/java/org/commonmark/node/BulletList.java | 12 ++++++++++++ .../src/main/java/org/commonmark/node/Code.java | 12 ++++++++++++ .../main/java/org/commonmark/node/CustomBlock.java | 3 +++ .../main/java/org/commonmark/node/CustomNode.java | 3 +++ .../src/main/java/org/commonmark/node/Document.java | 3 +++ .../src/main/java/org/commonmark/node/Emphasis.java | 8 ++++++++ .../java/org/commonmark/node/FencedCodeBlock.java | 12 ++++++++++++ .../main/java/org/commonmark/node/HardLineBreak.java | 10 ++++++++++ .../src/main/java/org/commonmark/node/Heading.java | 12 ++++++++++++ .../src/main/java/org/commonmark/node/Image.java | 8 ++++++++ .../java/org/commonmark/node/IndentedCodeBlock.java | 12 ++++++++++++ .../src/main/java/org/commonmark/node/Link.java | 2 +- .../org/commonmark/node/LinkReferenceDefinition.java | 2 +- .../src/main/java/org/commonmark/node/ListBlock.java | 3 +++ .../src/main/java/org/commonmark/node/ListItem.java | 5 +++++ .../main/java/org/commonmark/node/OrderedList.java | 12 ++++++++++++ .../src/main/java/org/commonmark/node/Paragraph.java | 2 ++ .../main/java/org/commonmark/node/SoftLineBreak.java | 9 +++++++++ .../java/org/commonmark/node/StrongEmphasis.java | 8 ++++++++ .../src/main/java/org/commonmark/node/Text.java | 10 ++++++++++ .../main/java/org/commonmark/node/ThematicBreak.java | 12 ++++++++++++ 23 files changed, 169 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a1535e11..0808edb72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ with the exception that 0.x versions can break between minor versions. ## [Unreleased] ### Added +- More documentation with examples for `Node` classes ### Changed ### Fixed - `MarkdownRenderer`: Fix precedence for `nodeRendererFactory`: Factories passed diff --git a/commonmark/src/main/java/org/commonmark/node/BlockQuote.java b/commonmark/src/main/java/org/commonmark/node/BlockQuote.java index 160f25ae2..f68252398 100644 --- a/commonmark/src/main/java/org/commonmark/node/BlockQuote.java +++ b/commonmark/src/main/java/org/commonmark/node/BlockQuote.java @@ -1,5 +1,15 @@ package org.commonmark.node; +/** + * A block quote, e.g.: + *
    + * > Some quoted text
    + * 
    + *

    + * Note that child nodes are themselves blocks, e.g. {@link Paragraph}, {@link ListBlock} etc. + * + * @see CommonMark Spec + */ public class BlockQuote extends Block { @Override diff --git a/commonmark/src/main/java/org/commonmark/node/BulletList.java b/commonmark/src/main/java/org/commonmark/node/BulletList.java index 4d5c2a894..014f4d3b2 100644 --- a/commonmark/src/main/java/org/commonmark/node/BulletList.java +++ b/commonmark/src/main/java/org/commonmark/node/BulletList.java @@ -1,5 +1,17 @@ package org.commonmark.node; +/** + * A bullet list, e.g.: + *

    + * - One
    + * - Two
    + * - Three
    + * 
    + *

    + * The children are {@link ListItem} blocks, which contain other blocks (or nested lists). + * + * @see CommonMark Spec: List items + */ public class BulletList extends ListBlock { private String marker; diff --git a/commonmark/src/main/java/org/commonmark/node/Code.java b/commonmark/src/main/java/org/commonmark/node/Code.java index 0b47ecb71..3b79e0c9c 100644 --- a/commonmark/src/main/java/org/commonmark/node/Code.java +++ b/commonmark/src/main/java/org/commonmark/node/Code.java @@ -1,5 +1,13 @@ package org.commonmark.node; +/** + * Inline code span, e.g.: + *

    + * Some `inline code`
    + * 
    + * + * @see CommonMark Spec + */ public class Code extends Node { private String literal; @@ -16,6 +24,10 @@ public void accept(Visitor visitor) { visitor.visit(this); } + /** + * @return the literal text in the code span (note that it's not necessarily the raw text between tildes, + * e.g. when spaces are stripped) + */ public String getLiteral() { return literal; } diff --git a/commonmark/src/main/java/org/commonmark/node/CustomBlock.java b/commonmark/src/main/java/org/commonmark/node/CustomBlock.java index 6596ec1a0..cad88933a 100644 --- a/commonmark/src/main/java/org/commonmark/node/CustomBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/CustomBlock.java @@ -1,5 +1,8 @@ package org.commonmark.node; +/** + * A block that extensions can subclass to define custom blocks (not part of the core specification). + */ public abstract class CustomBlock extends Block { @Override diff --git a/commonmark/src/main/java/org/commonmark/node/CustomNode.java b/commonmark/src/main/java/org/commonmark/node/CustomNode.java index a68e5cc11..88f0254da 100644 --- a/commonmark/src/main/java/org/commonmark/node/CustomNode.java +++ b/commonmark/src/main/java/org/commonmark/node/CustomNode.java @@ -1,5 +1,8 @@ package org.commonmark.node; +/** + * A node that extensions can subclass to define custom nodes (not part of the core specification). + */ public abstract class CustomNode extends Node { @Override public void accept(Visitor visitor) { diff --git a/commonmark/src/main/java/org/commonmark/node/Document.java b/commonmark/src/main/java/org/commonmark/node/Document.java index 5b7e74189..b4968c206 100644 --- a/commonmark/src/main/java/org/commonmark/node/Document.java +++ b/commonmark/src/main/java/org/commonmark/node/Document.java @@ -1,5 +1,8 @@ package org.commonmark.node; +/** + * The root block of a document, containing the top-level blocks. + */ public class Document extends Block { @Override diff --git a/commonmark/src/main/java/org/commonmark/node/Emphasis.java b/commonmark/src/main/java/org/commonmark/node/Emphasis.java index 9877e7b63..5efc8c327 100644 --- a/commonmark/src/main/java/org/commonmark/node/Emphasis.java +++ b/commonmark/src/main/java/org/commonmark/node/Emphasis.java @@ -1,5 +1,13 @@ package org.commonmark.node; +/** + * Emphasis, e.g.: + *
    + * Some *emphasis* or _emphasis_
    + * 
    + * + * @see CommonMark Spec: Emphasis and strong emphasis + */ public class Emphasis extends Node implements Delimited { private String delimiter; diff --git a/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java b/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java index 314c7457d..0e279a470 100644 --- a/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java @@ -1,5 +1,17 @@ package org.commonmark.node; +/** + * A fenced code block, e.g.: + *
    + * ```
    + * foo
    + * bar
    + * ```
    + * 
    + *

    + * + * @see CommonMark Spec + */ public class FencedCodeBlock extends Block { private String fenceCharacter; diff --git a/commonmark/src/main/java/org/commonmark/node/HardLineBreak.java b/commonmark/src/main/java/org/commonmark/node/HardLineBreak.java index 0640fc3c4..28874ec01 100644 --- a/commonmark/src/main/java/org/commonmark/node/HardLineBreak.java +++ b/commonmark/src/main/java/org/commonmark/node/HardLineBreak.java @@ -1,5 +1,15 @@ package org.commonmark.node; +/** + * A hard line break, e.g.: + *

    + * line\
    + * break
    + * 
    + *

    + * + * @see CommonMark Spec + */ public class HardLineBreak extends Node { @Override diff --git a/commonmark/src/main/java/org/commonmark/node/Heading.java b/commonmark/src/main/java/org/commonmark/node/Heading.java index 41f3b2504..5369d8739 100644 --- a/commonmark/src/main/java/org/commonmark/node/Heading.java +++ b/commonmark/src/main/java/org/commonmark/node/Heading.java @@ -1,5 +1,17 @@ package org.commonmark.node; +/** + * A heading, e.g.: + *

    + * First heading
    + * =============
    + *
    + * ## Another heading
    + * 
    + * + * @see CommonMark Spec: ATX headings + * @see CommonMark Spec: Setext headings + */ public class Heading extends Block { private int level; diff --git a/commonmark/src/main/java/org/commonmark/node/Image.java b/commonmark/src/main/java/org/commonmark/node/Image.java index 63481773a..1b31f6020 100644 --- a/commonmark/src/main/java/org/commonmark/node/Image.java +++ b/commonmark/src/main/java/org/commonmark/node/Image.java @@ -1,5 +1,13 @@ package org.commonmark.node; +/** + * An image, e.g.: + *
    + * ![foo](/url "title")
    + * 
    + * + * @see CommonMark Spec + */ public class Image extends Node { private String destination; diff --git a/commonmark/src/main/java/org/commonmark/node/IndentedCodeBlock.java b/commonmark/src/main/java/org/commonmark/node/IndentedCodeBlock.java index ccafca943..97642b7f3 100644 --- a/commonmark/src/main/java/org/commonmark/node/IndentedCodeBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/IndentedCodeBlock.java @@ -1,5 +1,17 @@ package org.commonmark.node; +/** + * An indented code block, e.g.: + *
    
    + * Code follows:
    + *
    + *     foo
    + *     bar
    + * 
    + *

    + * + * @see CommonMark Spec + */ public class IndentedCodeBlock extends Block { private String literal; diff --git a/commonmark/src/main/java/org/commonmark/node/Link.java b/commonmark/src/main/java/org/commonmark/node/Link.java index 6341fed13..4edc7f676 100644 --- a/commonmark/src/main/java/org/commonmark/node/Link.java +++ b/commonmark/src/main/java/org/commonmark/node/Link.java @@ -18,7 +18,7 @@ * Note that the text in the link can contain inline formatting, so it could also contain an {@link Image} or * {@link Emphasis}, etc. * - * @see CommonMark Spec for links + * @see CommonMark Spec */ public class Link extends Node { diff --git a/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java b/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java index 412a3c38d..b866781f0 100644 --- a/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java +++ b/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java @@ -9,7 +9,7 @@ * They can be referenced anywhere else in the document to produce a link using [foo]. The definitions * themselves are usually not rendered in the final output. * - * @see Link reference definitions + * @see CommonMark Spec */ public class LinkReferenceDefinition extends Block { diff --git a/commonmark/src/main/java/org/commonmark/node/ListBlock.java b/commonmark/src/main/java/org/commonmark/node/ListBlock.java index 1a7c8b2fd..1290bc622 100644 --- a/commonmark/src/main/java/org/commonmark/node/ListBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/ListBlock.java @@ -1,5 +1,8 @@ package org.commonmark.node; +/** + * A list block like {@link BulletList} or {@link OrderedList}. + */ public abstract class ListBlock extends Block { private boolean tight; diff --git a/commonmark/src/main/java/org/commonmark/node/ListItem.java b/commonmark/src/main/java/org/commonmark/node/ListItem.java index 4e63b6145..e488e8fbe 100644 --- a/commonmark/src/main/java/org/commonmark/node/ListItem.java +++ b/commonmark/src/main/java/org/commonmark/node/ListItem.java @@ -1,5 +1,10 @@ package org.commonmark.node; +/** + * A child of a {@link ListBlock}, containing other blocks (e.g. {@link Paragraph}, other lists, etc). + * + * @see CommonMark Spec: List items + */ public class ListItem extends Block { private Integer markerIndent; diff --git a/commonmark/src/main/java/org/commonmark/node/OrderedList.java b/commonmark/src/main/java/org/commonmark/node/OrderedList.java index 0bbe09917..61f8902c0 100644 --- a/commonmark/src/main/java/org/commonmark/node/OrderedList.java +++ b/commonmark/src/main/java/org/commonmark/node/OrderedList.java @@ -1,5 +1,17 @@ package org.commonmark.node; +/** + * An ordered list, e.g.: + *

    
    + * 1. One
    + * 2. Two
    + * 3. Three
    + * 
    + *

    + * The children are {@link ListItem} blocks, which contain other blocks (or nested lists). + * + * @see CommonMark Spec: List items + */ public class OrderedList extends ListBlock { private String markerDelimiter; diff --git a/commonmark/src/main/java/org/commonmark/node/Paragraph.java b/commonmark/src/main/java/org/commonmark/node/Paragraph.java index 176eaaa76..b298f1ce4 100644 --- a/commonmark/src/main/java/org/commonmark/node/Paragraph.java +++ b/commonmark/src/main/java/org/commonmark/node/Paragraph.java @@ -2,6 +2,8 @@ /** * A paragraph block, contains inline nodes such as {@link Text} + * + * @see CommonMark Spec */ public class Paragraph extends Block { diff --git a/commonmark/src/main/java/org/commonmark/node/SoftLineBreak.java b/commonmark/src/main/java/org/commonmark/node/SoftLineBreak.java index e66458912..87445db56 100644 --- a/commonmark/src/main/java/org/commonmark/node/SoftLineBreak.java +++ b/commonmark/src/main/java/org/commonmark/node/SoftLineBreak.java @@ -1,5 +1,14 @@ package org.commonmark.node; +/** + * A soft line break (as opposed to a {@link HardLineBreak}), e.g. between: + *

    + * foo
    + * bar
    + * 
    + * + * @see CommonMark Spec + */ public class SoftLineBreak extends Node { @Override diff --git a/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java b/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java index dbff571cd..0dbeed3df 100644 --- a/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java +++ b/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java @@ -1,5 +1,13 @@ package org.commonmark.node; +/** + * Strong emphasis, e.g.: + *
    
    + * Some **strong emphasis** or __strong emphasis__
    + * 
    + * + * @see CommonMark Spec: Emphasis and strong emphasis + */ public class StrongEmphasis extends Node implements Delimited { private String delimiter; diff --git a/commonmark/src/main/java/org/commonmark/node/Text.java b/commonmark/src/main/java/org/commonmark/node/Text.java index f16fc907b..9a04c41c1 100644 --- a/commonmark/src/main/java/org/commonmark/node/Text.java +++ b/commonmark/src/main/java/org/commonmark/node/Text.java @@ -1,5 +1,15 @@ package org.commonmark.node; +/** + * A text node, e.g. in: + *
    + * foo *bar*
    + * 
    + *

    + * The foo is a text node, and the bar inside the emphasis is also a text node. + * + * @see CommonMark Spec + */ public class Text extends Node { private String literal; diff --git a/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java b/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java index 836f8dfa1..a31131e07 100644 --- a/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java +++ b/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java @@ -1,5 +1,17 @@ package org.commonmark.node; +/** + * A thematic break, e.g. between text: + *

    + * Some text
    + *
    + * ___
    + *
    + * Some other text.
    + * 
    + * + * @see CommonMark Spec + */ public class ThematicBreak extends Block { private String literal; From df70419317a74fd0275a4055bde7b3d7d3f1af9b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 7 Jun 2025 17:40:10 +1000 Subject: [PATCH 193/345] Bump autolink to 0.12.0 https://github.com/robinst/autolink-java/blob/main/CHANGELOG.md#0120---2025-06-04 --- commonmark-ext-autolink/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index c2f9bf438..7f6a5cbc3 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -12,7 +12,7 @@ commonmark-java extension for turning plain URLs and email addresses into links - 0.11.0 + 0.12.0 From b79b949c105e783fa3e100de900c17e863704c4d Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 7 Jun 2025 17:43:11 +1000 Subject: [PATCH 194/345] CI: Test on Java 24 instead of 23 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24160d342..e94dabbba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [11, 17, 21, 23] + java: [11, 17, 21, 24] steps: - name: Checkout sources uses: actions/checkout@v4 From 71b5148d33dff4b9b671c46cbac7f5cb1bd16dcb Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Sun, 19 Jan 2025 00:04:13 +0100 Subject: [PATCH 195/345] add osgi headers via maven-bundle-plugin --- commonmark-ext-autolink/pom.xml | 9 ++++++ commonmark-ext-footnotes/pom.xml | 9 ++++++ commonmark-ext-gfm-strikethrough/pom.xml | 9 ++++++ commonmark-ext-gfm-tables/pom.xml | 9 ++++++ commonmark-ext-heading-anchor/pom.xml | 9 ++++++ commonmark-ext-image-attributes/pom.xml | 9 ++++++ commonmark-ext-ins/pom.xml | 9 ++++++ commonmark-ext-task-list-items/pom.xml | 9 ++++++ commonmark-ext-yaml-front-matter/pom.xml | 9 ++++++ commonmark/pom.xml | 9 ++++++ pom.xml | 36 ++++++++++++++++++++++++ 11 files changed, 126 insertions(+) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 7f6a5cbc3..b471e2e81 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -33,4 +33,13 @@ + + + + org.apache.felix + maven-bundle-plugin + + + + diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index d34e4e2f9..09b09935d 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -24,4 +24,13 @@ + + + + org.apache.felix + maven-bundle-plugin + + + + diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 1fd1269bf..23d93fa97 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -24,4 +24,13 @@ + + + + org.apache.felix + maven-bundle-plugin + + + + diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index d261a1d24..fc3f7957c 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -23,5 +23,14 @@ test + + + + + org.apache.felix + maven-bundle-plugin + + + diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index e4a62b2b3..b63b36472 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -23,5 +23,14 @@ test + + + + + org.apache.felix + maven-bundle-plugin + + + diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 55b0dce20..cbf102b8d 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -24,4 +24,13 @@ + + + + org.apache.felix + maven-bundle-plugin + + + + diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 9dbe2cf06..7af326271 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -24,4 +24,13 @@ + + + + org.apache.felix + maven-bundle-plugin + + + + diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 3926c8b5e..62fd974f5 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -24,4 +24,13 @@ + + + + org.apache.felix + maven-bundle-plugin + + + + diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 9e329e0d3..adff7d718 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -24,4 +24,13 @@ + + + + org.apache.felix + maven-bundle-plugin + + + + diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 17f3bc747..4ffd0ba95 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -54,6 +54,15 @@ + + + + org.apache.felix + maven-bundle-plugin + + + + BSD-2-Clause diff --git a/pom.xml b/pom.xml index 031869f6e..51583df21 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,42 @@ maven-surefire-plugin 3.2.5 + + org.apache.felix + maven-bundle-plugin + 6.0.0 + + + jar + bundle + + + + org.commonmark.*;-split-package:=merge-first + + + {local-packages} + + + + + + manifest + + manifest + + + true + + + + bundle + + bundle + + + + From e811c5cef0fbac91d25e581d75252f4bf99623aa Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Sun, 19 Jan 2025 10:26:46 +0100 Subject: [PATCH 196/345] downgrade maven-bundle-plugin to 5.1.9 as 6 requires java 17 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 51583df21..eea051811 100644 --- a/pom.xml +++ b/pom.xml @@ -79,7 +79,7 @@ org.apache.felix maven-bundle-plugin - 6.0.0 + 5.1.9 jar From 4e625178708118036b28ebee80d6520ebfe4b5d0 Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Sun, 19 Jan 2025 10:34:34 +0100 Subject: [PATCH 197/345] Add Lucee to README used by list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7660cc50c..73e69d9df 100644 --- a/README.md +++ b/README.md @@ -452,6 +452,7 @@ Some users of this library (feel free to raise a PR if you want to be added): * [Znai](https://github.com/testingisdocumenting/znai) * [Open Note](https://github.com/YangDai2003/OpenNote-Compose) a markdown editor and note-taking app for Android * [Quarkus Roq](https://github.com/quarkiverse/quarkus-roq/) The Roq Static Site Generator allows to easily create a static website or blog using Quarkus super-powers. +* [Lucee](https://github.com/lucee/lucee) See also -------- From 5fb452d97a7eb818ffad8f54a9ee3b2ef0577669 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 7 Jun 2025 17:48:46 +1000 Subject: [PATCH 198/345] Simplify maven-bundle-plugin setup The only downside to defining it in `` in the parent pom is that there's a warning when running the plugin on the parent pom: [INFO] --- bundle:5.1.9:manifest (bundle-manifest) @ commonmark-parent --- [WARNING] Ignoring project type pom - supportedProjectTypes = [jar, bundle] The upside is we don't have to remember to put the plugin into all the poms. The configuration is as recommended here: https://felix.apache.org/documentation/subprojects/apache-felix-maven-bundle-plugin-bnd.html#_adding_osgi_metadata_to_existing_projects_without_changing_the_packaging_type Compared to the previous one, the only differences are: - Import-Package adds imports for java packages, which seems to be recommended, see https://github.com/bndtools/bnd/issues/2507 - Import-Package of own packages that were exported (e.g. org.commonmark in core) don't have the version number, not sure what's recommended there --- commonmark-ext-autolink/pom.xml | 9 ---- commonmark-ext-footnotes/pom.xml | 9 ---- commonmark-ext-gfm-strikethrough/pom.xml | 9 ---- commonmark-ext-gfm-tables/pom.xml | 9 ---- commonmark-ext-heading-anchor/pom.xml | 9 ---- commonmark-ext-image-attributes/pom.xml | 9 ---- commonmark-ext-ins/pom.xml | 9 ---- commonmark-ext-task-list-items/pom.xml | 9 ---- commonmark-ext-yaml-front-matter/pom.xml | 9 ---- commonmark/pom.xml | 9 ---- pom.xml | 56 +++++++++--------------- 11 files changed, 20 insertions(+), 126 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index b471e2e81..7f6a5cbc3 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -33,13 +33,4 @@ - - - - org.apache.felix - maven-bundle-plugin - - - - diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 09b09935d..d34e4e2f9 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -24,13 +24,4 @@ - - - - org.apache.felix - maven-bundle-plugin - - - - diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 23d93fa97..1fd1269bf 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -24,13 +24,4 @@ - - - - org.apache.felix - maven-bundle-plugin - - - - diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index fc3f7957c..d261a1d24 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -23,14 +23,5 @@ test - - - - - org.apache.felix - maven-bundle-plugin - - - diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index b63b36472..e4a62b2b3 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -23,14 +23,5 @@ test - - - - - org.apache.felix - maven-bundle-plugin - - - diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index cbf102b8d..55b0dce20 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -24,13 +24,4 @@ - - - - org.apache.felix - maven-bundle-plugin - - - - diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 7af326271..9dbe2cf06 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -24,13 +24,4 @@ - - - - org.apache.felix - maven-bundle-plugin - - - - diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 62fd974f5..3926c8b5e 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -24,13 +24,4 @@ - - - - org.apache.felix - maven-bundle-plugin - - - - diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index adff7d718..9e329e0d3 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -24,13 +24,4 @@ - - - - org.apache.felix - maven-bundle-plugin - - - - diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 4ffd0ba95..17f3bc747 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -54,15 +54,6 @@ - - - - org.apache.felix - maven-bundle-plugin - - - - BSD-2-Clause diff --git a/pom.xml b/pom.xml index eea051811..29ca4ead2 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,11 @@ org.apache.maven.plugins maven-jar-plugin 3.4.1 + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + org.apache.maven.plugins @@ -76,42 +81,6 @@ maven-surefire-plugin 3.2.5 - - org.apache.felix - maven-bundle-plugin - 5.1.9 - - - jar - bundle - - - - org.commonmark.*;-split-package:=merge-first - - - {local-packages} - - - - - - manifest - - manifest - - - true - - - - bundle - - bundle - - - - @@ -139,6 +108,21 @@ deploy + + org.apache.felix + maven-bundle-plugin + + 5.1.9 + + + bundle-manifest + process-classes + + manifest + + + + From 664627727f1abe8f73f85391a659a0a17ea055ee Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 15 Jun 2025 21:19:02 +1000 Subject: [PATCH 199/345] Upgrade to JUnit 5 and AssertJ --- .../commonmark/ext/autolink/AutolinkTest.java | 44 +++--- .../footnotes/FootnoteHtmlRendererTest.java | 2 +- .../FootnoteMarkdownRendererTest.java | 6 +- .../ext/footnotes/FootnotesTest.java | 87 ++++++------ .../StrikethroughMarkdownRendererTest.java | 6 +- .../strikethrough/StrikethroughSpecTest.java | 23 ++-- .../gfm/strikethrough/StrikethroughTest.java | 15 +-- .../gfm/tables/TableMarkdownRendererTest.java | 6 +- .../ext/gfm/tables/TablesSpecTest.java | 23 ++-- .../commonmark/ext/gfm/tables/TablesTest.java | 63 +++++---- .../ext/gfm/tables/TablesTextContentTest.java | 2 +- .../HeadingAnchorConfigurationTest.java | 13 +- .../ext/heading/anchor/HeadingAnchorTest.java | 2 +- .../image/attributes/ImageAttributesTest.java | 7 +- .../ext/ins/InsMarkdownRendererTest.java | 6 +- .../java/org/commonmark/ext/ins/InsTest.java | 13 +- .../task/list/items/TaskListItemsTest.java | 2 +- .../ext/front/matter/YamlFrontMatterTest.java | 109 ++++++++------- .../integration/BoundsIntegrationTest.java | 33 ++--- .../ExtensionsIntegrationTest.java | 2 +- .../MarkdownRendererIntegrationTest.java | 6 +- .../SourceSpanIntegrationTest.java | 4 - .../integration/SpecIntegrationTest.java | 6 +- commonmark-test-util/pom.xml | 8 +- .../src/main/java/module-info.java | 3 +- .../java/org/commonmark/testutil/Asserts.java | 4 +- .../testutil/RenderingTestCase.java | 2 + .../org/commonmark/testutil/SpecTestCase.java | 28 ++-- .../testutil/example/ExampleReader.java | 15 +-- .../internal/DocumentParserTest.java | 20 ++- .../LinkReferenceDefinitionParserTest.java | 104 +++++++-------- .../internal/util/EscapingTest.java | 26 ++-- .../internal/util/LineReaderTest.java | 24 ++-- .../parser/InlineContentParserTest.java | 36 +++-- .../parser/beta/LinkProcessorTest.java | 13 +- .../commonmark/parser/beta/ScannerTest.java | 94 ++++++------- .../markdown/MarkdownRendererTest.java | 15 +-- .../markdown/SpecMarkdownRendererTest.java | 10 +- .../commonmark/test/AbstractVisitorTest.java | 11 +- .../org/commonmark/test/DelimitedTest.java | 22 +-- .../test/DelimiterProcessorTest.java | 24 ++-- .../test/FencedCodeBlockParserTest.java | 8 +- .../commonmark/test/HeadingParserTest.java | 2 +- .../commonmark/test/HtmlInlineParserTest.java | 2 +- .../org/commonmark/test/HtmlRendererTest.java | 125 ++++++++---------- .../test/InlineParserContextTest.java | 8 +- .../test/LinkReferenceDefinitionNodeTest.java | 66 +++++---- .../commonmark/test/ListBlockParserTest.java | 8 +- .../commonmark/test/ListTightLooseTest.java | 2 +- .../java/org/commonmark/test/ParserTest.java | 69 +++++----- .../org/commonmark/test/PathologicalTest.java | 25 +--- .../org/commonmark/test/RegressionTest.java | 31 ++--- .../org/commonmark/test/SourceLineTest.java | 19 +-- .../org/commonmark/test/SourceSpanTest.java | 71 +++++----- .../org/commonmark/test/SourceSpansTest.java | 68 +++++----- .../org/commonmark/test/SpecCoreTest.java | 8 +- .../org/commonmark/test/SpecCrLfCoreTest.java | 6 +- .../org/commonmark/test/SpecialInputTest.java | 2 +- .../test/TextContentRendererTest.java | 2 +- .../test/TextContentWriterTest.java | 14 +- .../test/ThematicBreakParserTest.java | 6 +- .../org/commonmark/test/UsageExampleTest.java | 29 ++-- .../org/commonmark/text/CharactersTest.java | 19 ++- pom.xml | 13 +- 64 files changed, 727 insertions(+), 815 deletions(-) diff --git a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java index 67fd69abe..338513f33 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -6,13 +6,12 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Set; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class AutolinkTest extends RenderingTestCase { @@ -71,44 +70,37 @@ public void sourceSpans() { Paragraph paragraph = (Paragraph) document.getFirstChild(); Text abc = (Text) paragraph.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 3)), - abc.getSourceSpans()); + assertThat(abc.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 3))); - assertTrue(abc.getNext() instanceof SoftLineBreak); + assertThat(abc.getNext()).isInstanceOf(SoftLineBreak.class); Link one = (Link) abc.getNext().getNext(); - assertEquals("http://example.com/one", one.getDestination()); - assertEquals(List.of(SourceSpan.of(1, 0, 4, 22)), - one.getSourceSpans()); + assertThat(one.getDestination()).isEqualTo("http://example.com/one"); + assertThat(one.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(1, 0, 4, 22))); - assertTrue(one.getNext() instanceof SoftLineBreak); + assertThat(one.getNext()).isInstanceOf(SoftLineBreak.class); Text def = (Text) one.getNext().getNext(); - assertEquals("def ", def.getLiteral()); - assertEquals(List.of(SourceSpan.of(2, 0, 27, 4)), - def.getSourceSpans()); + assertThat(def.getLiteral()).isEqualTo("def "); + assertThat(def.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 0, 27, 4))); Link two = (Link) def.getNext(); - assertEquals("http://example.com/two", two.getDestination()); - assertEquals(List.of(SourceSpan.of(2, 4, 31, 22)), - two.getSourceSpans()); + assertThat(two.getDestination()).isEqualTo("http://example.com/two"); + assertThat(two.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 4, 31, 22))); - assertTrue(two.getNext() instanceof SoftLineBreak); + assertThat(two.getNext()).isInstanceOf(SoftLineBreak.class); Text ghi = (Text) two.getNext().getNext(); - assertEquals("ghi ", ghi.getLiteral()); - assertEquals(List.of(SourceSpan.of(3, 0, 54, 4)), - ghi.getSourceSpans()); + assertThat(ghi.getLiteral()).isEqualTo("ghi "); + assertThat(ghi.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 0, 54, 4))); Link three = (Link) ghi.getNext(); - assertEquals("http://example.com/three", three.getDestination()); - assertEquals(List.of(SourceSpan.of(3, 4, 58, 24)), - three.getSourceSpans()); + assertThat(three.getDestination()).isEqualTo("http://example.com/three"); + assertThat(three.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 4, 58, 24))); Text jkl = (Text) three.getNext(); - assertEquals(" jkl", jkl.getLiteral()); - assertEquals(List.of(SourceSpan.of(3, 28, 82, 4)), - jkl.getSourceSpans()); + assertThat(jkl.getLiteral()).isEqualTo(" jkl"); + assertThat(jkl.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 28, 82, 4))); } @Override diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java index ce5fdd51a..bc7d4f74c 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java @@ -8,7 +8,7 @@ import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.Asserts; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Set; diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java index 9d567ece2..be3af6715 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java @@ -4,11 +4,11 @@ import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.markdown.MarkdownRenderer; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Set; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class FootnoteMarkdownRendererTest { private static final Set EXTENSIONS = Set.of(FootnotesExtension.builder().inlineFootnotes(true).build()); @@ -43,7 +43,7 @@ public void testInline() { private void assertRoundTrip(String input) { String rendered = parseAndRender(input); - assertEquals(input, rendered); + assertThat(rendered).isEqualTo(input); } private String parseAndRender(String source) { diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index bef6d89cb..3fa726d8f 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -4,15 +4,14 @@ import org.commonmark.node.*; import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public class FootnotesTest { @@ -24,13 +23,13 @@ public void testDefBlockStart() { for (var s : List.of("1", "a", "^", "*", "\\a", "\uD83D\uDE42", "&0")) { var doc = PARSER.parse("[^" + s + "]: footnote\n"); var def = find(doc, FootnoteDefinition.class); - assertEquals(s, def.getLabel()); + assertThat(def.getLabel()).isEqualTo(s); } for (var s : List.of("", " ", "a b", "]", "\r", "\n", "\t")) { var input = "[^" + s + "]: footnote\n"; var doc = PARSER.parse(input); - assertNull("input: " + input, tryFind(doc, FootnoteDefinition.class)); + assertThat(tryFind(doc, FootnoteDefinition.class)).as("input: " + input).isNull(); } } @@ -40,24 +39,24 @@ public void testDefBlockStartInterrupts() { var doc = PARSER.parse("test\n[^1]: footnote\n"); var paragraph = find(doc, Paragraph.class); var def = find(doc, FootnoteDefinition.class); - assertEquals("test", ((Text) paragraph.getLastChild()).getLiteral()); - assertEquals("1", def.getLabel()); + assertThat(((Text) paragraph.getLastChild()).getLiteral()).isEqualTo("test"); + assertThat(def.getLabel()).isEqualTo("1"); } @Test public void testDefBlockStartIndented() { var doc1 = PARSER.parse(" [^1]: footnote\n"); - assertEquals("1", find(doc1, FootnoteDefinition.class).getLabel()); + assertThat(find(doc1, FootnoteDefinition.class).getLabel()).isEqualTo("1"); var doc2 = PARSER.parse(" [^1]: footnote\n"); - assertNull(tryFind(doc2, FootnoteDefinition.class)); + assertNone(doc2, FootnoteDefinition.class); } @Test public void testDefMultiple() { var doc = PARSER.parse("[^1]: foo\n[^2]: bar\n"); var defs = findAll(doc, FootnoteDefinition.class); - assertEquals("1", defs.get(0).getLabel()); - assertEquals("2", defs.get(1).getLabel()); + assertThat(defs.get(0).getLabel()).isEqualTo("1"); + assertThat(defs.get(1).getLabel()).isEqualTo("2"); } @Test @@ -65,8 +64,8 @@ public void testDefBlockStartAfterLinkReferenceDefinition() { var doc = PARSER.parse("[foo]: /url\n[^1]: footnote\n"); var linkReferenceDef = find(doc, LinkReferenceDefinition.class); var footnotesDef = find(doc, FootnoteDefinition.class); - assertEquals("foo", linkReferenceDef.getLabel()); - assertEquals("1", footnotesDef.getLabel()); + assertThat(linkReferenceDef.getLabel()).isEqualTo("foo"); + assertThat(footnotesDef.getLabel()).isEqualTo("1"); } @Test @@ -90,14 +89,14 @@ public void testDefContainsIndentedCodeBlock() { var doc = PARSER.parse("[^1]:\n code\n"); var def = find(doc, FootnoteDefinition.class); var codeBlock = (IndentedCodeBlock) def.getFirstChild(); - assertEquals("code\n", codeBlock.getLiteral()); + assertThat(codeBlock.getLiteral()).isEqualTo("code\n"); } @Test public void testDefContainsMultipleLines() { var doc = PARSER.parse("[^1]: footnote\nstill\n"); var def = find(doc, FootnoteDefinition.class); - assertEquals("1", def.getLabel()); + assertThat(def.getLabel()).isEqualTo("1"); var paragraph = (Paragraph) def.getFirstChild(); assertText("footnote", paragraph.getFirstChild()); assertText("still", paragraph.getLastChild()); @@ -107,7 +106,7 @@ public void testDefContainsMultipleLines() { public void testDefContainsList() { var doc = PARSER.parse("[^1]: - foo\n - bar\n"); var def = find(doc, FootnoteDefinition.class); - assertEquals("1", def.getLabel()); + assertThat(def.getLabel()).isEqualTo("1"); var list = (BulletList) def.getFirstChild(); var item1 = (ListItem) list.getFirstChild(); var item2 = (ListItem) list.getLastChild(); @@ -120,7 +119,7 @@ public void testDefInterruptedByOthers() { var doc = PARSER.parse("[^1]: footnote\n# Heading\n"); var def = find(doc, FootnoteDefinition.class); var heading = find(doc, Heading.class); - assertEquals("1", def.getLabel()); + assertThat(def.getLabel()).isEqualTo("1"); assertText("Heading", heading.getFirstChild()); } @@ -128,13 +127,13 @@ public void testDefInterruptedByOthers() { public void testReference() { var doc = PARSER.parse("Test [^foo]\n\n[^foo]: /url\n"); var ref = find(doc, FootnoteReference.class); - assertEquals("foo", ref.getLabel()); + assertThat(ref.getLabel()).isEqualTo("foo"); } @Test public void testReferenceNoDefinition() { var doc = PARSER.parse("Test [^foo]\n"); - assertNull(tryFind(doc, FootnoteReference.class)); + assertNone(doc, FootnoteReference.class); } @Test @@ -142,13 +141,13 @@ public void testRefWithEmphasisInside() { // No emphasis inside footnote reference, should just be treated as text var doc = PARSER.parse("Test [^*foo*]\n\n[^*foo*]: def\n"); var ref = find(doc, FootnoteReference.class); - assertEquals("*foo*", ref.getLabel()); - assertNull(ref.getFirstChild()); + assertThat(ref.getLabel()).isEqualTo("*foo*"); + assertThat(ref.getFirstChild()).isNull(); var paragraph = doc.getFirstChild(); var text = (Text) paragraph.getFirstChild(); - assertEquals("Test ", text.getLiteral()); - assertEquals(ref, text.getNext()); - assertEquals(ref, paragraph.getLastChild()); + assertThat(text.getLiteral()).isEqualTo("Test "); + assertThat(text.getNext()).isEqualTo(ref); + assertThat(paragraph.getLastChild()).isEqualTo(ref); } @Test @@ -156,18 +155,18 @@ public void testRefWithEmphasisAround() { // Emphasis around footnote reference, the * inside needs to be removed from emphasis processing var doc = PARSER.parse("Test *abc [^foo*] def*\n\n[^foo*]: def\n"); var ref = find(doc, FootnoteReference.class); - assertEquals("foo*", ref.getLabel()); + assertThat(ref.getLabel()).isEqualTo("foo*"); assertText("abc ", ref.getPrevious()); assertText(" def", ref.getNext()); var em = find(doc, Emphasis.class); - assertEquals(em, ref.getParent()); + assertThat(ref.getParent()).isEqualTo(em); } @Test public void testRefAfterBang() { var doc = PARSER.parse("Test![^foo]\n\n[^foo]: def\n"); var ref = find(doc, FootnoteReference.class); - assertEquals("foo", ref.getLabel()); + assertThat(ref.getLabel()).isEqualTo("foo"); var paragraph = doc.getFirstChild(); assertText("Test!", paragraph.getFirstChild()); } @@ -178,7 +177,7 @@ public void testRefAsLabelOnly() { // resolve as footnotes. If `[foo][^bar]` fails to parse as a bracket, `[^bar]` by itself needs to be tried. var doc = PARSER.parse("Test [foo][^bar]\n\n[^bar]: footnote\n"); var ref = find(doc, FootnoteReference.class); - assertEquals("bar", ref.getLabel()); + assertThat(ref.getLabel()).isEqualTo("bar"); var paragraph = doc.getFirstChild(); assertText("Test [foo]", paragraph.getFirstChild()); } @@ -188,7 +187,7 @@ public void testRefWithEmptyLabel() { // [^bar] is a footnote but [] is just text, because collapsed reference links don't resolve as footnotes var doc = PARSER.parse("Test [^bar][]\n\n[^bar]: footnote\n"); var ref = find(doc, FootnoteReference.class); - assertEquals("bar", ref.getLabel()); + assertThat(ref.getLabel()).isEqualTo("bar"); var paragraph = doc.getFirstChild(); assertText("Test ", paragraph.getFirstChild()); assertText("[]", paragraph.getLastChild()); @@ -198,22 +197,22 @@ public void testRefWithEmptyLabel() { public void testRefWithBracket() { // Not a footnote, [ needs to be escaped var doc = PARSER.parse("Test [^f[oo]\n\n[^f[oo]: /url\n"); - assertNull(tryFind(doc, FootnoteReference.class)); + assertNone(doc, FootnoteReference.class); } @Test public void testRefWithBackslash() { var doc = PARSER.parse("[^\\foo]\n\n[^\\foo]: note\n"); var ref = find(doc, FootnoteReference.class); - assertEquals("\\foo", ref.getLabel()); + assertThat(ref.getLabel()).isEqualTo("\\foo"); var def = find(doc, FootnoteDefinition.class); - assertEquals("\\foo", def.getLabel()); + assertThat(def.getLabel()).isEqualTo("\\foo"); } @Test public void testPreferInlineLink() { var doc = PARSER.parse("Test [^bar](/url)\n\n[^bar]: footnote\n"); - assertNull(tryFind(doc, FootnoteReference.class)); + assertNone(doc, FootnoteReference.class); } @Test @@ -221,7 +220,7 @@ public void testPreferReferenceLink() { // This is tricky because `[^*foo*][foo]` is a valid link already. If `[foo]` was not defined, the first bracket // would be a footnote. var doc = PARSER.parse("Test [^*foo*][foo]\n\n[^*foo*]: /url\n\n[foo]: /url"); - assertNull(tryFind(doc, FootnoteReference.class)); + assertNone(doc, FootnoteReference.class); } @Test @@ -229,7 +228,7 @@ public void testReferenceLinkWithoutDefinition() { // Similar to previous test but there's no definition var doc = PARSER.parse("Test [^*foo*][foo]\n\n[^*foo*]: def\n"); var ref = find(doc, FootnoteReference.class); - assertEquals("*foo*", ref.getLabel()); + assertThat(ref.getLabel()).isEqualTo("*foo*"); var paragraph = (Paragraph) doc.getFirstChild(); assertText("Test ", paragraph.getFirstChild()); assertText("[foo]", paragraph.getLastChild()); @@ -249,12 +248,12 @@ public void testInlineFootnote() { { var doc = parser.parse("Test \\^[not inline footnote]"); - assertNull(tryFind(doc, InlineFootnote.class)); + assertNone(doc, InlineFootnote.class); } { var doc = parser.parse("Test ^[not inline footnote"); - assertNull(tryFind(doc, InlineFootnote.class)); + assertNone(doc, InlineFootnote.class); var t = doc.getFirstChild().getFirstChild(); assertText("Test ^[not inline footnote", t); } @@ -269,7 +268,7 @@ public void testInlineFootnote() { var fn = find(doc, InlineFootnote.class); assertText("test ", fn.getFirstChild()); var code = fn.getFirstChild().getNext(); - assertEquals("bla]", ((Code) code).getLiteral()); + assertThat(((Code) code).getLiteral()).isEqualTo("bla]"); } { @@ -277,7 +276,7 @@ public void testInlineFootnote() { var fn = find(doc, InlineFootnote.class); assertText("with a ", fn.getFirstChild()); var link = fn.getFirstChild().getNext(); - assertEquals("url", ((Link) link).getDestination()); + assertThat(((Link) link).getDestination()).isEqualTo("url"); } } @@ -287,10 +286,14 @@ public void testSourcePositions() { var doc = parser.parse("Test [^foo]\n\n[^foo]: /url\n"); var ref = find(doc, FootnoteReference.class); - assertEquals(ref.getSourceSpans(), List.of(SourceSpan.of(0, 5, 5, 6))); + assertThat(ref.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 5, 5, 6))); var def = find(doc, FootnoteDefinition.class); - assertEquals(def.getSourceSpans(), List.of(SourceSpan.of(2, 0, 13, 12))); + assertThat(def.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 0, 13, 12))); + } + + private static void assertNone(Node parent, Class nodeClass) { + assertThat(tryFind(parent, nodeClass)).as(() -> "Node " + parent + " containing " + nodeClass).isNull(); } private static T find(Node parent, Class nodeClass) { @@ -315,6 +318,6 @@ private static List findAll(Node parent, Class nodeClass) { private static void assertText(String expected, Node node) { var text = (Text) node; - assertEquals(expected, text.getLiteral()); + assertThat(text.getLiteral()).isEqualTo(expected); } } diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java index 507fc0a88..c497a4db3 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java @@ -3,11 +3,11 @@ import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.markdown.MarkdownRenderer; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Set; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class StrikethroughMarkdownRendererTest { @@ -30,6 +30,6 @@ protected String render(String source) { private void assertRoundTrip(String input) { String rendered = render(input); - assertEquals(input, rendered); + assertThat(rendered).isEqualTo(input); } } diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java index 2ca5471b0..f1199b521 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java @@ -7,30 +7,27 @@ import org.commonmark.testutil.TestResources; import org.commonmark.testutil.example.Example; import org.commonmark.testutil.example.ExampleReader; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.util.List; import java.util.Set; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class StrikethroughSpecTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(StrikethroughExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); - private final Example example; + @Parameter + Example example; - public StrikethroughSpecTest(Example example) { - this.example = example; - } - - @Parameters(name = "{0}") - public static List data() { - return ExampleReader.readExampleObjects(TestResources.getGfmSpec(), "strikethrough"); + static List data() { + return ExampleReader.readExamples(TestResources.getGfmSpec(), "strikethrough"); } @Test diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java index cb3019957..c29391cdd 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java @@ -12,12 +12,12 @@ import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Set; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class StrikethroughTest extends RenderingTestCase { @@ -84,14 +84,14 @@ public void insideBlockQuote() { public void delimited() { Node document = PARSER.parse("~~foo~~"); Strikethrough strikethrough = (Strikethrough) document.getFirstChild().getFirstChild(); - assertEquals("~~", strikethrough.getOpeningDelimiter()); - assertEquals("~~", strikethrough.getClosingDelimiter()); + assertThat(strikethrough.getOpeningDelimiter()).isEqualTo("~~"); + assertThat(strikethrough.getClosingDelimiter()).isEqualTo("~~"); } @Test public void textContentRenderer() { Node document = PARSER.parse("~~foo~~"); - assertEquals("/foo/", CONTENT_RENDERER.render(document)); + assertThat(CONTENT_RENDERER.render(document)).isEqualTo("/foo/"); } @Test @@ -104,7 +104,7 @@ public void requireTwoTildesOption() { .build(); Node document = parser.parse("~foo~ ~~bar~~"); - assertEquals("(sub)foo(/sub) /bar/", CONTENT_RENDERER.render(document)); + assertThat(CONTENT_RENDERER.render(document)).isEqualTo("(sub)foo(/sub) /bar/"); } @Test @@ -117,8 +117,7 @@ public void sourceSpans() { Node document = parser.parse("hey ~~there~~\n"); Paragraph block = (Paragraph) document.getFirstChild(); Node strikethrough = block.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 4, 4, 9)), - strikethrough.getSourceSpans()); + assertThat(strikethrough.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 4, 4, 9))); } @Override diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java index 16303b58c..85c11206c 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java @@ -3,11 +3,11 @@ import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.markdown.MarkdownRenderer; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Set; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TableMarkdownRendererTest { @@ -70,6 +70,6 @@ protected String render(String source) { private void assertRoundTrip(String input) { String rendered = render(input); - assertEquals(input, rendered); + assertThat(rendered).isEqualTo(input); } } diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java index d5ea8c836..e7f3db4d1 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java @@ -7,30 +7,27 @@ import org.commonmark.testutil.TestResources; import org.commonmark.testutil.example.Example; import org.commonmark.testutil.example.ExampleReader; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.util.List; import java.util.Set; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class TablesSpecTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(TablesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); - private final Example example; + @Parameter + Example example; - public TablesSpecTest(Example example) { - this.example = example; - } - - @Parameters(name = "{0}") - public static List data() { - return ExampleReader.readExampleObjects(TestResources.getGfmSpec(), "table"); + static List data() { + return ExampleReader.readExamples(TestResources.getGfmSpec(), "table"); } @Test diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java index 57f4a4ae5..3135b4d8e 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -10,13 +10,13 @@ import org.commonmark.renderer.html.AttributeProviderFactory; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Set; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TablesTest extends RenderingTestCase { @@ -730,7 +730,7 @@ public void setAttributes(Node node, String tagName, Map attribu .extensions(EXTENSIONS) .build(); String rendered = renderer.render(PARSER.parse("Abc|Def\n---|---\n1|2")); - assertThat(rendered, is("\n" + + assertThat(rendered).isEqualTo("
    \n" + "\n" + "\n" + "\n" + @@ -743,7 +743,7 @@ public void setAttributes(Node node, String tagName, Map attribu "\n" + "\n" + "\n" + - "
    Abc2
    \n")); + "\n"); } @Test @@ -766,7 +766,7 @@ public void setAttributes(Node node, String tagName, Map attribu .extensions(EXTENSIONS) .build(); String rendered = renderer.render(PARSER.parse("Abc|Def\n-----|---\n1|2")); - assertThat(rendered, is("\n" + + assertThat(rendered).isEqualTo("
    \n" + "\n" + "\n" + "\n" + @@ -779,7 +779,7 @@ public void setAttributes(Node node, String tagName, Map attribu "\n" + "\n" + "\n" + - "
    Abc2
    \n")); + "\n"); } @Test @@ -791,49 +791,48 @@ public void sourceSpans() { Node document = parser.parse("Abc|Def\n---|---\n|1|2\n 3|four|\n|||\n"); TableBlock block = (TableBlock) document.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 7), - SourceSpan.of(2, 0, 16, 4), SourceSpan.of(3, 0, 21, 8), SourceSpan.of(4, 0, 30, 3)), - block.getSourceSpans()); + assertThat(block.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 7), + SourceSpan.of(2, 0, 16, 4), SourceSpan.of(3, 0, 21, 8), SourceSpan.of(4, 0, 30, 3))); TableHead head = (TableHead) block.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 7)), head.getSourceSpans()); + assertThat(head.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 7))); TableRow headRow = (TableRow) head.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 7)), headRow.getSourceSpans()); + assertThat(headRow.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 7))); TableCell headRowCell1 = (TableCell) headRow.getFirstChild(); TableCell headRowCell2 = (TableCell) headRow.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 3)), headRowCell1.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 3)), headRowCell1.getFirstChild().getSourceSpans()); - assertEquals(List.of(SourceSpan.of(0, 4, 4, 3)), headRowCell2.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(0, 4, 4, 3)), headRowCell2.getFirstChild().getSourceSpans()); + assertThat(headRowCell1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 3))); + assertThat(headRowCell1.getFirstChild().getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 3))); + assertThat(headRowCell2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 4, 4, 3))); + assertThat(headRowCell2.getFirstChild().getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 4, 4, 3))); TableBody body = (TableBody) block.getLastChild(); - assertEquals(List.of(SourceSpan.of(2, 0, 16, 4), SourceSpan.of(3, 0, 21, 8), SourceSpan.of(4, 0, 30, 3)), body.getSourceSpans()); + assertThat(body.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 0, 16, 4), SourceSpan.of(3, 0, 21, 8), SourceSpan.of(4, 0, 30, 3))); TableRow bodyRow1 = (TableRow) body.getFirstChild(); - assertEquals(List.of(SourceSpan.of(2, 0, 16, 4)), bodyRow1.getSourceSpans()); + assertThat(bodyRow1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 0, 16, 4))); TableCell bodyRow1Cell1 = (TableCell) bodyRow1.getFirstChild(); TableCell bodyRow1Cell2 = (TableCell) bodyRow1.getLastChild(); - assertEquals(List.of(SourceSpan.of(2, 1, 17, 1)), bodyRow1Cell1.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(2, 1, 17, 1)), bodyRow1Cell1.getFirstChild().getSourceSpans()); - assertEquals(List.of(SourceSpan.of(2, 3, 19, 1)), bodyRow1Cell2.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(2, 3, 19, 1)), bodyRow1Cell2.getFirstChild().getSourceSpans()); + assertThat(bodyRow1Cell1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 1, 17, 1))); + assertThat(bodyRow1Cell1.getFirstChild().getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 1, 17, 1))); + assertThat(bodyRow1Cell2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 3, 19, 1))); + assertThat(bodyRow1Cell2.getFirstChild().getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 3, 19, 1))); TableRow bodyRow2 = (TableRow) body.getFirstChild().getNext(); - assertEquals(List.of(SourceSpan.of(3, 0, 21, 8)), bodyRow2.getSourceSpans()); + assertThat(bodyRow2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 0, 21, 8))); TableCell bodyRow2Cell1 = (TableCell) bodyRow2.getFirstChild(); TableCell bodyRow2Cell2 = (TableCell) bodyRow2.getLastChild(); - assertEquals(List.of(SourceSpan.of(3, 1, 22, 1)), bodyRow2Cell1.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(3, 1, 22, 1)), bodyRow2Cell1.getFirstChild().getSourceSpans()); - assertEquals(List.of(SourceSpan.of(3, 3, 24, 4)), bodyRow2Cell2.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(3, 3, 24, 4)), bodyRow2Cell2.getFirstChild().getSourceSpans()); + assertThat(bodyRow2Cell1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 1, 22, 1))); + assertThat(bodyRow2Cell1.getFirstChild().getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 1, 22, 1))); + assertThat(bodyRow2Cell2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 3, 24, 4))); + assertThat(bodyRow2Cell2.getFirstChild().getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 3, 24, 4))); TableRow bodyRow3 = (TableRow) body.getLastChild(); - assertEquals(List.of(SourceSpan.of(4, 0, 30, 3)), bodyRow3.getSourceSpans()); + assertThat(bodyRow3.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(4, 0, 30, 3))); TableCell bodyRow3Cell1 = (TableCell) bodyRow3.getFirstChild(); TableCell bodyRow3Cell2 = (TableCell) bodyRow3.getLastChild(); - assertEquals(List.of(), bodyRow3Cell1.getSourceSpans()); - assertEquals(List.of(), bodyRow3Cell2.getSourceSpans()); + assertThat(bodyRow3Cell1.getSourceSpans()).isEqualTo(List.of()); + assertThat(bodyRow3Cell2.getSourceSpans()).isEqualTo(List.of()); } @Override diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java index c5ef8cb5a..966f097fd 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java @@ -5,7 +5,7 @@ import org.commonmark.renderer.text.LineBreakRendering; import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.testutil.Asserts; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Set; diff --git a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java index af2ae13cf..438a3a9bd 100644 --- a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java @@ -3,12 +3,11 @@ import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; public class HeadingAnchorConfigurationTest { @@ -30,25 +29,25 @@ public void testDefaultConfigurationHasNoAdditions() { HtmlRenderer renderer = HtmlRenderer.builder() .extensions(List.of(HeadingAnchorExtension.create())) .build(); - assertThat(doRender(renderer, "# "), equalTo("

    \n")); + assertThat(doRender(renderer, "# ")).isEqualTo("

    \n"); } @Test public void testDefaultIdWhenNoTextOnHeader() { HtmlRenderer renderer = buildRenderer("defid", "", ""); - assertThat(doRender(renderer, "# "), equalTo("

    \n")); + assertThat(doRender(renderer, "# ")).isEqualTo("

    \n"); } @Test public void testPrefixAddedToHeader() { HtmlRenderer renderer = buildRenderer("", "pre-", ""); - assertThat(doRender(renderer, "# text"), equalTo("

    text

    \n")); + assertThat(doRender(renderer, "# text")).isEqualTo("

    text

    \n"); } @Test public void testSuffixAddedToHeader() { HtmlRenderer renderer = buildRenderer("", "", "-post"); - assertThat(doRender(renderer, "# text"), equalTo("

    text

    \n")); + assertThat(doRender(renderer, "# text")).isEqualTo("

    text

    \n"); } private String doRender(HtmlRenderer renderer, String text) { diff --git a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java index 872306bed..3149542e3 100644 --- a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java @@ -4,7 +4,7 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Set; diff --git a/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java b/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java index b863bd4b7..3edf8497e 100644 --- a/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java +++ b/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java @@ -8,12 +8,12 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Set; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class ImageAttributesTest extends RenderingTestCase { @@ -131,8 +131,7 @@ public void sourceSpans() { Node document = parser.parse("x{height=3 width=4}\n"); Paragraph block = (Paragraph) document.getFirstChild(); Node text = block.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 19)), - text.getSourceSpans()); + assertThat(text.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 19))); } @Override diff --git a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java index 28f2cd354..6fc9ead67 100644 --- a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java +++ b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java @@ -3,11 +3,11 @@ import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.markdown.MarkdownRenderer; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Set; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class InsMarkdownRendererTest { @@ -28,6 +28,6 @@ protected String render(String source) { private void assertRoundTrip(String input) { String rendered = render(input); - assertEquals(input, rendered); + assertThat(rendered).isEqualTo(input); } } diff --git a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java index 4603da60b..a5c91a395 100644 --- a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java +++ b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java @@ -9,12 +9,12 @@ import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Set; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class InsTest extends RenderingTestCase { @@ -82,14 +82,14 @@ public void insideBlockQuote() { public void delimited() { Node document = PARSER.parse("++foo++"); Ins ins = (Ins) document.getFirstChild().getFirstChild(); - assertEquals("++", ins.getOpeningDelimiter()); - assertEquals("++", ins.getClosingDelimiter()); + assertThat(ins.getOpeningDelimiter()).isEqualTo("++"); + assertThat(ins.getClosingDelimiter()).isEqualTo("++"); } @Test public void textContentRenderer() { Node document = PARSER.parse("++foo++"); - assertEquals("foo", CONTENT_RENDERER.render(document)); + assertThat(CONTENT_RENDERER.render(document)).isEqualTo("foo"); } @Test @@ -102,8 +102,7 @@ public void sourceSpans() { Node document = parser.parse("hey ++there++\n"); Paragraph block = (Paragraph) document.getFirstChild(); Node ins = block.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 4, 4, 9)), - ins.getSourceSpans()); + assertThat(ins.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 4, 4, 9))); } @Override diff --git a/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java index c13e10bb7..0adc615a7 100644 --- a/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java +++ b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java @@ -4,7 +4,7 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Set; diff --git a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java index f46c11b3c..db17d4a4e 100644 --- a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java +++ b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java @@ -6,14 +6,13 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; import java.util.Set; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class YamlFrontMatterTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(YamlFrontMatterExtension.create()); @@ -31,10 +30,10 @@ public void simpleValue() { Map> data = getFrontMatter(input); - assertEquals(1, data.size()); - assertEquals("hello", data.keySet().iterator().next()); - assertEquals(1, data.get("hello").size()); - assertEquals("world", data.get("hello").get(0)); + assertThat(data).hasSize(1); + assertThat(data.keySet().iterator().next()).isEqualTo("hello"); + assertThat(data.get("hello")).hasSize(1); + assertThat(data.get("hello").get(0)).isEqualTo("world"); assertRendering(input, rendered); } @@ -50,9 +49,9 @@ public void emptyValue() { Map> data = getFrontMatter(input); - assertEquals(1, data.size()); - assertEquals("key", data.keySet().iterator().next()); - assertEquals(0, data.get("key").size()); + assertThat(data).hasSize(1); + assertThat(data.keySet().iterator().next()).isEqualTo("key"); + assertThat(data.get("key")).hasSize(0); assertRendering(input, rendered); } @@ -70,11 +69,11 @@ public void listValues() { Map> data = getFrontMatter(input); - assertEquals(1, data.size()); - assertTrue(data.containsKey("list")); - assertEquals(2, data.get("list").size()); - assertEquals("value1", data.get("list").get(0)); - assertEquals("value2", data.get("list").get(1)); + assertThat(data).hasSize(1); + assertThat(data).containsKey("list"); + assertThat(data.get("list")).hasSize(2); + assertThat(data.get("list").get(0)).isEqualTo("value1"); + assertThat(data.get("list").get(1)).isEqualTo("value2"); assertRendering(input, rendered); } @@ -92,10 +91,10 @@ public void literalValue1() { Map> data = getFrontMatter(input); - assertEquals(1, data.size()); - assertTrue(data.containsKey("literal")); - assertEquals(1, data.get("literal").size()); - assertEquals("hello markdown!\nliteral thing...", data.get("literal").get(0)); + assertThat(data).hasSize(1); + assertThat(data).containsKey("literal"); + assertThat(data.get("literal")).hasSize(1); + assertThat(data.get("literal").get(0)).isEqualTo("hello markdown!\nliteral thing..."); assertRendering(input, rendered); } @@ -112,10 +111,10 @@ public void literalValue2() { Map> data = getFrontMatter(input); - assertEquals(1, data.size()); - assertTrue(data.containsKey("literal")); - assertEquals(1, data.get("literal").size()); - assertEquals("- hello markdown!", data.get("literal").get(0)); + assertThat(data).hasSize(1); + assertThat(data).containsKey("literal"); + assertThat(data.get("literal")).hasSize(1); + assertThat(data.get("literal").get(0)).isEqualTo("- hello markdown!"); assertRendering(input, rendered); } @@ -137,20 +136,20 @@ public void complexValues() { Map> data = getFrontMatter(input); - assertEquals(3, data.size()); + assertThat(data).hasSize(3); - assertTrue(data.containsKey("simple")); - assertEquals(1, data.get("simple").size()); - assertEquals("value", data.get("simple").get(0)); + assertThat(data).containsKey("simple"); + assertThat(data.get("simple")).hasSize(1); + assertThat(data.get("simple").get(0)).isEqualTo("value"); - assertTrue(data.containsKey("literal")); - assertEquals(1, data.get("literal").size()); - assertEquals("hello markdown!\n\nliteral literal", data.get("literal").get(0)); + assertThat(data).containsKey("literal"); + assertThat(data.get("literal")).hasSize(1); + assertThat(data.get("literal").get(0)).isEqualTo("hello markdown!\n\nliteral literal"); - assertTrue(data.containsKey("list")); - assertEquals(2, data.get("list").size()); - assertEquals("value1", data.get("list").get(0)); - assertEquals("value2", data.get("list").get(1)); + assertThat(data).containsKey("list"); + assertThat(data.get("list")).hasSize(2); + assertThat(data.get("list").get(0)).isEqualTo("value1"); + assertThat(data.get("list").get(1)).isEqualTo("value2"); assertRendering(input, rendered); } @@ -164,7 +163,7 @@ public void empty() { Map> data = getFrontMatter(input); - assertTrue(data.isEmpty()); + assertThat(data).isEmpty(); assertRendering(input, rendered); } @@ -180,7 +179,7 @@ public void yamlInParagraph() { Map> data = getFrontMatter(input); - assertTrue(data.isEmpty()); + assertThat(data).isEmpty(); assertRendering(input, rendered); } @@ -195,7 +194,7 @@ public void yamlOnSecondLine() { Map> data = getFrontMatter(input); - assertTrue(data.isEmpty()); + assertThat(data).isEmpty(); assertRendering(input, rendered); } @@ -208,7 +207,7 @@ public void nonMatchedStartTag() { Map> data = getFrontMatter(input); - assertTrue(data.isEmpty()); + assertThat(data).isEmpty(); assertRendering(input, rendered); } @@ -222,7 +221,7 @@ public void inList() { Map> data = getFrontMatter(input); - assertTrue(data.isEmpty()); + assertThat(data).isEmpty(); assertRendering(input, rendered); } @@ -240,9 +239,9 @@ public void visitorIgnoresOtherCustomNodes() { document.accept(visitor); Map> data = visitor.getData(); - assertEquals(1, data.size()); - assertTrue(data.containsKey("hello")); - assertEquals(List.of("world"), data.get("hello")); + assertThat(data).hasSize(1); + assertThat(data).containsKey("hello"); + assertThat(data.get("hello")).isEqualTo(List.of("world")); } @Test @@ -261,9 +260,9 @@ public void nodesCanBeModified() { document.accept(visitor); Map> data = visitor.getData(); - assertEquals(1, data.size()); - assertTrue(data.containsKey("see")); - assertEquals(List.of("you"), data.get("see")); + assertThat(data).hasSize(1); + assertThat(data).containsKey("see"); + assertThat(data.get("see")).isEqualTo(List.of("you")); } @Test @@ -275,10 +274,10 @@ public void dotInKeys() { Map> data = getFrontMatter(input); - assertEquals(1, data.size()); - assertEquals("ms.author", data.keySet().iterator().next()); - assertEquals(1, data.get("ms.author").size()); - assertEquals("author", data.get("ms.author").get(0)); + assertThat(data).hasSize(1); + assertThat(data.keySet().iterator().next()).isEqualTo("ms.author"); + assertThat(data.get("ms.author")).hasSize(1); + assertThat(data.get("ms.author").get(0)).isEqualTo("author"); } @Test @@ -292,9 +291,9 @@ public void singleQuotedLiterals() { Map> data = getFrontMatter(input); - assertEquals(2, data.size()); - assertEquals("It's me", data.get("string").get(0)); - assertEquals("I'm here", data.get("list").get(0)); + assertThat(data).hasSize(2); + assertThat(data.get("string").get(0)).isEqualTo("It's me"); + assertThat(data.get("list").get(0)).isEqualTo("I'm here"); } @Test @@ -308,9 +307,9 @@ public void doubleQuotedLiteral() { Map> data = getFrontMatter(input); - assertEquals(2, data.size()); - assertEquals("backslash: \\ quote: \"", data.get("string").get(0)); - assertEquals("hey", data.get("list").get(0)); + assertThat(data).hasSize(2); + assertThat(data.get("string").get(0)).isEqualTo("backslash: \\ quote: \""); + assertThat(data.get("list").get(0)).isEqualTo("hey"); } @Override diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/BoundsIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/BoundsIntegrationTest.java index 8ee15164a..f1259b825 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/BoundsIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/BoundsIntegrationTest.java @@ -3,39 +3,30 @@ import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.testutil.TestResources; -import org.commonmark.testutil.example.Example; import org.commonmark.testutil.example.ExampleReader; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; -import java.util.ArrayList; import java.util.List; -import static org.junit.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests various substrings of the spec examples to check for out of bounds exceptions. */ -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class BoundsIntegrationTest { private static final Parser PARSER = Parser.builder().build(); - protected final String input; + @Parameter + String input; - public BoundsIntegrationTest(String input) { - this.input = input; - } - - @Parameterized.Parameters(name = "{0}") - public static List data() { - List examples = ExampleReader.readExamples(TestResources.getSpec()); - List data = new ArrayList<>(); - for (Example example : examples) { - data.add(new Object[]{example.getSource()}); - } - return data; + static List data() { + return ExampleReader.readExampleSources(TestResources.getSpec()); } @Test @@ -54,7 +45,7 @@ private void parse(String input) { try { Node parsed = PARSER.parse(input); // Parsing should always return a node - assertNotNull(parsed); + assertThat(parsed).isNotNull(); } catch (Exception e) { throw new AssertionError("Parsing failed, input: " + input, e); } diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java index 64cf60ed9..523154d2c 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java @@ -3,7 +3,7 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Tests to ensure all extensions work well together. diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java index f05efe1c3..9ab5c32a3 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java @@ -10,11 +10,11 @@ import org.commonmark.ext.task.list.items.TaskListItemsExtension; import org.commonmark.parser.Parser; import org.commonmark.renderer.markdown.MarkdownRenderer; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class MarkdownRendererIntegrationTest { @@ -40,6 +40,6 @@ private String render(String source) { private void assertRoundTrip(String input) { String rendered = render(input); - assertEquals(input, rendered); + assertThat(rendered).isEqualTo(input); } } diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java index a0649f537..171cc51b1 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java @@ -14,10 +14,6 @@ public class SourceSpanIntegrationTest extends SpecIntegrationTest { .includeSourceSpans(IncludeSourceSpans.BLOCKS) .build(); - public SourceSpanIntegrationTest(Example example) { - super(example); - } - @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java index 2b615aa41..07853d402 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java @@ -4,7 +4,7 @@ import org.commonmark.parser.Parser; import org.commonmark.testutil.example.Example; import org.commonmark.testutil.SpecTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.*; @@ -20,10 +20,6 @@ public class SpecIntegrationTest extends SpecTestCase { protected static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(Extensions.ALL_EXTENSIONS).percentEncodeUrls(true).build(); protected static final Map OVERRIDDEN_EXAMPLES = getOverriddenExamples(); - public SpecIntegrationTest(Example example) { - super(example); - } - @Test public void testHtmlRendering() { String expectedHtml = OVERRIDDEN_EXAMPLES.get(example.getSource()); diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 7fb4881f1..d6a38f4a9 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -13,8 +13,12 @@ - junit - junit + org.junit.jupiter + junit-jupiter + + + org.assertj + assertj-core diff --git a/commonmark-test-util/src/main/java/module-info.java b/commonmark-test-util/src/main/java/module-info.java index ef983a513..12980d80a 100644 --- a/commonmark-test-util/src/main/java/module-info.java +++ b/commonmark-test-util/src/main/java/module-info.java @@ -2,5 +2,6 @@ exports org.commonmark.testutil; exports org.commonmark.testutil.example; - requires junit; + requires org.assertj.core; + requires org.junit.jupiter.params; } diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/Asserts.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/Asserts.java index 64124b129..971a1b4ea 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/Asserts.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/Asserts.java @@ -1,13 +1,13 @@ package org.commonmark.testutil; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class Asserts { public static void assertRendering(String source, String expectedRendering, String actualRendering) { // include source for better assertion errors String expected = showTabs(expectedRendering + "\n\n" + source); String actual = showTabs(actualRendering + "\n\n" + source); - assertEquals(expected, actual); + assertThat(actual).isEqualTo(expected); } private static String showTabs(String s) { diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java index b585f4604..f7da4c008 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java @@ -1,5 +1,7 @@ package org.commonmark.testutil; +import static org.assertj.core.api.Assertions.assertThat; + public abstract class RenderingTestCase { protected abstract String render(String source); diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java index 2fb175772..c29a6a69a 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java @@ -2,30 +2,22 @@ import org.commonmark.testutil.example.Example; import org.commonmark.testutil.example.ExampleReader; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import java.util.ArrayList; import java.util.List; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public abstract class SpecTestCase { - protected final Example example; + @Parameter + protected Example example; - public SpecTestCase(Example example) { - this.example = example; + static List data() { + return ExampleReader.readExamples(TestResources.getSpec()); } - - @Parameters(name = "{0}") - public static List data() { - List examples = ExampleReader.readExamples(TestResources.getSpec()); - List data = new ArrayList<>(); - for (Example example : examples) { - data.add(new Object[]{example}); - } - return data; - } - } diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java index 6f5dd6276..d40a10f63 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Reader for files containing examples of CommonMark source and the expected HTML rendering (e.g. spec.txt). @@ -42,15 +43,13 @@ public static List readExamples(URL url) { } } + public static List readExamples(URL url, String info) { + var examples = readExamples(url); + return examples.stream().filter(e -> e.getInfo().contains(info)).collect(Collectors.toList()); + } + public static List readExampleObjects(URL url, String info) { - List examples = readExamples(url); - List data = new ArrayList<>(); - for (Example example : examples) { - if (example.getInfo().contains(info)) { - data.add(new Object[]{example}); - } - } - return data; + return readExamples(url, info).stream().map(e -> new Object[]{e}).collect(Collectors.toList()); } public static List readExampleSources(URL url) { diff --git a/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java b/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java index 45143b852..a834665ff 100644 --- a/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java @@ -2,17 +2,15 @@ import org.commonmark.node.*; import org.commonmark.parser.block.BlockParserFactory; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.HashSet; import java.util.List; import java.util.Set; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; -public class DocumentParserTest { +class DocumentParserTest { private static final List CORE_FACTORIES = List.of( new BlockQuoteParser.Factory(), new HeadingParser.Factory(), @@ -23,28 +21,28 @@ public class DocumentParserTest { new IndentedCodeBlockParser.Factory()); @Test - public void calculateBlockParserFactories_givenAFullListOfAllowedNodes_includesAllCoreFactories() { + void calculateBlockParserFactories_givenAFullListOfAllowedNodes_includesAllCoreFactories() { List customParserFactories = List.of(); var enabledBlockTypes = Set.of(BlockQuote.class, Heading.class, FencedCodeBlock.class, HtmlBlock.class, ThematicBreak.class, ListBlock.class, IndentedCodeBlock.class); List blockParserFactories = DocumentParser.calculateBlockParserFactories(customParserFactories, enabledBlockTypes); - assertThat(blockParserFactories.size(), is(CORE_FACTORIES.size())); + assertThat(blockParserFactories).hasSameSizeAs(CORE_FACTORIES); for (BlockParserFactory factory : CORE_FACTORIES) { - assertTrue(hasInstance(blockParserFactories, factory.getClass())); + assertThat(hasInstance(blockParserFactories, factory.getClass())).isTrue(); } } @Test - public void calculateBlockParserFactories_givenAListOfAllowedNodes_includesAssociatedFactories() { + void calculateBlockParserFactories_givenAListOfAllowedNodes_includesAssociatedFactories() { List customParserFactories = List.of(); Set> nodes = new HashSet<>(); nodes.add(IndentedCodeBlock.class); List blockParserFactories = DocumentParser.calculateBlockParserFactories(customParserFactories, nodes); - assertThat(blockParserFactories.size(), is(1)); - assertTrue(hasInstance(blockParserFactories, IndentedCodeBlockParser.Factory.class)); + assertThat(blockParserFactories).hasSize(1); + assertThat(hasInstance(blockParserFactories, IndentedCodeBlockParser.Factory.class)).isTrue(); } private boolean hasInstance(List blockParserFactories, Class factoryClass) { diff --git a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java index 3f22adac6..b69ada0e9 100644 --- a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java @@ -3,82 +3,82 @@ import org.commonmark.internal.LinkReferenceDefinitionParser.State; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.SourceLine; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; -public class LinkReferenceDefinitionParserTest { +class LinkReferenceDefinitionParserTest { private final LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); @Test - public void testStartLabel() { + void testStartLabel() { assertState("[", State.LABEL, "["); } @Test - public void testStartNoLabel() { + void testStartNoLabel() { // Not a label assertParagraph("a"); // Can not go back to parsing link reference definitions parse("a"); parse("["); - assertEquals(State.PARAGRAPH, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.PARAGRAPH); assertParagraphLines("a\n[", parser); } @Test - public void testEmptyLabel() { + void testEmptyLabel() { assertParagraph("[]: /"); assertParagraph("[ ]: /"); assertParagraph("[ \t\n\u000B\f\r ]: /"); } @Test - public void testLabelColon() { + void testLabelColon() { assertParagraph("[foo] : /"); } @Test - public void testLabel() { + void testLabel() { assertState("[foo]:", State.DESTINATION, "[foo]:"); assertState("[ foo ]:", State.DESTINATION, "[ foo ]:"); } @Test - public void testLabelInvalid() { + void testLabelInvalid() { assertParagraph("[foo[]:"); } @Test - public void testLabelMultiline() { + void testLabelMultiline() { parse("[two"); - assertEquals(State.LABEL, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.LABEL); parse("lines]:"); - assertEquals(State.DESTINATION, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.DESTINATION); parse("/url"); - assertEquals(State.START_TITLE, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.START_TITLE); assertDef(parser.getDefinitions().get(0), "two\nlines", "/url", null); } @Test - public void testLabelStartsWithNewline() { + void testLabelStartsWithNewline() { parse("["); - assertEquals(State.LABEL, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.LABEL); parse("weird]:"); - assertEquals(State.DESTINATION, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.DESTINATION); parse("/url"); - assertEquals(State.START_TITLE, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.START_TITLE); assertDef(parser.getDefinitions().get(0), "\nweird", "/url", null); } @Test - public void testDestination() { + void testDestination() { parse("[foo]: /url"); - assertEquals(State.START_TITLE, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.START_TITLE); assertParagraphLines("", parser); - assertEquals(1, parser.getDefinitions().size()); + assertThat(parser.getDefinitions()).hasSize(1); assertDef(parser.getDefinitions().get(0), "foo", "/url", null); parse("[bar]: "); @@ -86,91 +86,91 @@ public void testDestination() { } @Test - public void testDestinationInvalid() { + void testDestinationInvalid() { assertParagraph("[foo]: "); } @Test - public void testTitle() { + void testTitle() { parse("[foo]: /url 'title'"); - assertEquals(State.START_DEFINITION, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.START_DEFINITION); assertParagraphLines("", parser); - assertEquals(1, parser.getDefinitions().size()); + assertThat(parser.getDefinitions()).hasSize(1); assertDef(parser.getDefinitions().get(0), "foo", "/url", "title"); } @Test - public void testTitleStartWhitespace() { + void testTitleStartWhitespace() { parse("[foo]: /url"); - assertEquals(State.START_TITLE, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.START_TITLE); assertParagraphLines("", parser); parse(" "); - assertEquals(State.START_DEFINITION, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.START_DEFINITION); assertParagraphLines(" ", parser); - assertEquals(1, parser.getDefinitions().size()); + assertThat(parser.getDefinitions()).hasSize(1); assertDef(parser.getDefinitions().get(0), "foo", "/url", null); } @Test - public void testTitleMultiline() { + void testTitleMultiline() { parse("[foo]: /url 'two"); - assertEquals(State.TITLE, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.TITLE); assertParagraphLines("[foo]: /url 'two", parser); - assertEquals(0, parser.getDefinitions().size()); + assertThat(parser.getDefinitions()).isEmpty(); parse("lines"); - assertEquals(State.TITLE, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.TITLE); assertParagraphLines("[foo]: /url 'two\nlines", parser); - assertEquals(0, parser.getDefinitions().size()); + assertThat(parser.getDefinitions()).isEmpty(); parse("'"); - assertEquals(State.START_DEFINITION, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.START_DEFINITION); assertParagraphLines("", parser); - assertEquals(1, parser.getDefinitions().size()); + assertThat(parser.getDefinitions()).hasSize(1); assertDef(parser.getDefinitions().get(0), "foo", "/url", "two\nlines\n"); } @Test - public void testTitleMultiline2() { + void testTitleMultiline2() { parse("[foo]: /url '"); - assertEquals(State.TITLE, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.TITLE); parse("title'"); - assertEquals(State.START_DEFINITION, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.START_DEFINITION); assertDef(parser.getDefinitions().get(0), "foo", "/url", "\ntitle"); } @Test - public void testTitleMultiline3() { + void testTitleMultiline3() { parse("[foo]: /url"); - assertEquals(State.START_TITLE, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.START_TITLE); // Note that this looks like a valid title until we parse "bad", at which point we need to treat the whole line // as a paragraph line and discard any already parsed title. parse("\"title\" bad"); - assertEquals(State.PARAGRAPH, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.PARAGRAPH); assertDef(parser.getDefinitions().get(0), "foo", "/url", null); } @Test - public void testTitleMultiline4() { + void testTitleMultiline4() { parse("[foo]: /url"); - assertEquals(State.START_TITLE, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.START_TITLE); parse("(title"); - assertEquals(State.TITLE, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.TITLE); parse("foo("); - assertEquals(State.PARAGRAPH, parser.getState()); + assertThat(parser.getState()).isEqualTo(State.PARAGRAPH); assertDef(parser.getDefinitions().get(0), "foo", "/url", null); } @Test - public void testTitleInvalid() { + void testTitleInvalid() { assertParagraph("[foo]: /url (invalid("); assertParagraph("[foo]: 'title'"); assertParagraph("[foo]: /url 'title' INVALID"); @@ -188,18 +188,18 @@ private static void assertState(String input, State state, String paragraphConte LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); // TODO: Should we check things with source spans here? parser.parse(SourceLine.of(input, null)); - assertEquals(state, parser.getState()); + assertThat(parser.getState()).isEqualTo(state); assertParagraphLines(paragraphContent, parser); } private static void assertDef(LinkReferenceDefinition def, String label, String destination, String title) { - assertEquals(label, def.getLabel()); - assertEquals(destination, def.getDestination()); - assertEquals(title, def.getTitle()); + assertThat(def.getLabel()).isEqualTo(label); + assertThat(def.getDestination()).isEqualTo(destination); + assertThat(def.getTitle()).isEqualTo(title); } private static void assertParagraphLines(String expectedContent, LinkReferenceDefinitionParser parser) { String actual = parser.getParagraphLines().getContent(); - assertEquals(expectedContent, actual); + assertThat(actual).isEqualTo(expectedContent); } } diff --git a/commonmark/src/test/java/org/commonmark/internal/util/EscapingTest.java b/commonmark/src/test/java/org/commonmark/internal/util/EscapingTest.java index 9433eb7d0..eb2f1a801 100644 --- a/commonmark/src/test/java/org/commonmark/internal/util/EscapingTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/util/EscapingTest.java @@ -1,21 +1,21 @@ package org.commonmark.internal.util; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; -public class EscapingTest { +class EscapingTest { @Test - public void testEscapeHtml() { - assertEquals("nothing to escape", Escaping.escapeHtml("nothing to escape")); - assertEquals("&", Escaping.escapeHtml("&")); - assertEquals("<", Escaping.escapeHtml("<")); - assertEquals(">", Escaping.escapeHtml(">")); - assertEquals(""", Escaping.escapeHtml("\"")); - assertEquals("< start", Escaping.escapeHtml("< start")); - assertEquals("end >", Escaping.escapeHtml("end >")); - assertEquals("< both >", Escaping.escapeHtml("< both >")); - assertEquals("< middle & too >", Escaping.escapeHtml("< middle & too >")); + void testEscapeHtml() { + assertThat(Escaping.escapeHtml("nothing to escape")).isEqualTo("nothing to escape"); + assertThat(Escaping.escapeHtml("&")).isEqualTo("&"); + assertThat(Escaping.escapeHtml("<")).isEqualTo("<"); + assertThat(Escaping.escapeHtml(">")).isEqualTo(">"); + assertThat(Escaping.escapeHtml("\"")).isEqualTo("""); + assertThat(Escaping.escapeHtml("< start")).isEqualTo("< start"); + assertThat(Escaping.escapeHtml("end >")).isEqualTo("end >"); + assertThat(Escaping.escapeHtml("< both >")).isEqualTo("< both >"); + assertThat(Escaping.escapeHtml("< middle & too >")).isEqualTo("< middle & too >"); } } diff --git a/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java b/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java index fc48623a8..b52713846 100644 --- a/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java @@ -1,6 +1,6 @@ package org.commonmark.internal.util; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.*; import java.nio.charset.StandardCharsets; @@ -9,13 +9,14 @@ import java.util.Objects; import static java.util.stream.Collectors.joining; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.commonmark.internal.util.LineReader.CHAR_BUFFER_SIZE; -import static org.junit.Assert.*; -public class LineReaderTest { +class LineReaderTest { @Test - public void testReadLine() throws IOException { + void testReadLine() throws IOException { assertLines(); assertLines("", "\n"); @@ -48,21 +49,16 @@ public void testReadLine() throws IOException { } @Test - public void testClose() throws IOException { + void testClose() throws IOException { var reader = new InputStreamReader(new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8))); var lineReader = new LineReader(reader); lineReader.close(); lineReader.close(); - try { - reader.read(); - fail("Expected read to throw after closing reader"); - } catch (IOException e) { - // Expected - } + assertThatThrownBy(reader::read).isInstanceOf(IOException.class); } private void assertLines(String... s) throws IOException { - assertTrue("Expected parts needs to be even (pairs of content and terminator)", s.length % 2 == 0); + assertThat(s.length).as("Expected parts needs to be even (pairs of content and terminator)").isEven(); var input = Arrays.stream(s).filter(Objects::nonNull).collect(joining("")); assertLines(new StringReader(input), s); @@ -77,8 +73,8 @@ private static void assertLines(Reader reader, String... expectedParts) throws I lines.add(line); lines.add(lineReader.getLineTerminator()); } - assertNull(lineReader.getLineTerminator()); - assertEquals(Arrays.asList(expectedParts), lines); + assertThat(lineReader.getLineTerminator()).isNull(); + assertThat(lines).containsExactly(expectedParts); } } diff --git a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java index ed97b6f31..d0f45a6bc 100644 --- a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java @@ -1,53 +1,51 @@ package org.commonmark.parser; -import org.commonmark.node.CustomNode; -import org.commonmark.node.Heading; -import org.commonmark.node.Image; -import org.commonmark.node.Text; +import org.commonmark.node.*; import org.commonmark.parser.beta.InlineContentParser; import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.beta.InlineParserState; import org.commonmark.parser.beta.ParsedInline; import org.commonmark.test.Nodes; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Set; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; -public class InlineContentParserTest { +class InlineContentParserTest { @Test - public void customInlineContentParser() { + void customInlineContentParser() { var parser = Parser.builder().customInlineContentParserFactory(new DollarInlineParser.Factory()).build(); var doc = parser.parse("Test: $hey *there*$ $you$\n\n# Heading $heading$\n"); var inline1 = Nodes.find(doc, DollarInline.class); - assertEquals("hey *there*", inline1.getLiteral()); + assertThat(inline1.getLiteral()).isEqualTo("hey *there*"); var inline2 = (DollarInline) doc.getFirstChild().getLastChild(); - assertEquals("you", inline2.getLiteral()); + assertThat(inline2.getLiteral()).isEqualTo("you"); var heading = Nodes.find(doc, Heading.class); var inline3 = (DollarInline) heading.getLastChild(); - assertEquals("heading", inline3.getLiteral()); + assertThat(inline3.getLiteral()).isEqualTo("heading"); // Parser is created for each inline snippet, which is why the index resets for the second snippet. - assertEquals(0, inline1.getIndex()); - assertEquals(1, inline2.getIndex()); - assertEquals(0, inline3.getIndex()); + assertThat(inline1.getIndex()).isEqualTo(0); + assertThat(inline2.getIndex()).isEqualTo(1); + assertThat(inline3.getIndex()).isEqualTo(0); } @Test - public void bangInlineContentParser() { + void bangInlineContentParser() { // See if using ! for a custom inline content parser works. // ![] is used for images, but if it's not followed by a [, it should be possible to parse it differently. var parser = Parser.builder().customInlineContentParserFactory(new BangInlineParser.Factory()).build(); var doc = parser.parse("![image](url) !notimage"); var image = Nodes.find(doc, Image.class); - assertEquals("url", image.getDestination()); - assertEquals(" ", ((Text) image.getNext()).getLiteral()); - assertEquals(BangInline.class, image.getNext().getNext().getClass()); - assertEquals("notimage", ((Text) image.getNext().getNext().getNext()).getLiteral()); + assertThat(image.getDestination()).isEqualTo("url"); + assertThat(((Text) image.getNext()).getLiteral()).isEqualTo(" "); + // Class + assertThat(image.getNext().getNext()).isInstanceOf(BangInline.class); + assertThat(((Text) image.getNext().getNext().getNext()).getLiteral()).isEqualTo("notimage"); } private static class DollarInline extends CustomNode { diff --git a/commonmark/src/test/java/org/commonmark/parser/beta/LinkProcessorTest.java b/commonmark/src/test/java/org/commonmark/parser/beta/LinkProcessorTest.java index 6209ac9a0..ef8739128 100644 --- a/commonmark/src/test/java/org/commonmark/parser/beta/LinkProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/beta/LinkProcessorTest.java @@ -4,13 +4,14 @@ import org.commonmark.node.Text; import org.commonmark.parser.Parser; import org.commonmark.test.Nodes; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; + +class LinkProcessorTest { -public class LinkProcessorTest { @Test - public void testLinkMarkerShouldNotBeIncludedByDefault() { + void testLinkMarkerShouldNotBeIncludedByDefault() { // If a link marker is registered but is not processed, the built-in link processor shouldn't consume it. // And I think by default, other processors shouldn't consume it either (by accident). // So requiring processors to opt into including the marker is better than requiring them to opt out, @@ -19,7 +20,7 @@ public void testLinkMarkerShouldNotBeIncludedByDefault() { var parser = Parser.builder().linkMarker('^').build(); var doc = parser.parse("^[test](url)"); var link = Nodes.find(doc, Link.class); - assertEquals("url", link.getDestination()); - assertEquals("^", ((Text) link.getPrevious()).getLiteral()); + assertThat(link.getDestination()).isEqualTo("url"); + assertThat(((Text) link.getPrevious()).getLiteral()).isEqualTo("^"); } } diff --git a/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java b/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java index 14c118ab9..bd74cab0e 100644 --- a/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java @@ -3,87 +3,87 @@ import org.commonmark.node.SourceSpan; import org.commonmark.parser.SourceLine; import org.commonmark.parser.SourceLines; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.assertThat; -public class ScannerTest { +class ScannerTest { @Test - public void testNext() { + void testNext() { Scanner scanner = new Scanner(List.of( SourceLine.of("foo bar", null)), 0, 4); - assertEquals('b', scanner.peek()); + assertThat(scanner.peek()).isEqualTo('b'); scanner.next(); - assertEquals('a', scanner.peek()); + assertThat(scanner.peek()).isEqualTo('a'); scanner.next(); - assertEquals('r', scanner.peek()); + assertThat(scanner.peek()).isEqualTo('r'); scanner.next(); - assertEquals('\0', scanner.peek()); + assertThat(scanner.peek()).isEqualTo('\0'); } @Test - public void testMultipleLines() { + void testMultipleLines() { Scanner scanner = new Scanner(List.of( SourceLine.of("ab", null), SourceLine.of("cde", null)), 0, 0); - assertTrue(scanner.hasNext()); - assertEquals('\0', scanner.peekPreviousCodePoint()); - assertEquals('a', scanner.peek()); + assertThat(scanner.hasNext()).isTrue(); + assertThat(scanner.peekPreviousCodePoint()).isEqualTo('\0'); + assertThat(scanner.peek()).isEqualTo('a'); scanner.next(); - assertTrue(scanner.hasNext()); - assertEquals('a', scanner.peekPreviousCodePoint()); - assertEquals('b', scanner.peek()); + assertThat(scanner.hasNext()).isTrue(); + assertThat(scanner.peekPreviousCodePoint()).isEqualTo('a'); + assertThat(scanner.peek()).isEqualTo('b'); scanner.next(); - assertTrue(scanner.hasNext()); - assertEquals('b', scanner.peekPreviousCodePoint()); - assertEquals('\n', scanner.peek()); + assertThat(scanner.hasNext()).isTrue(); + assertThat(scanner.peekPreviousCodePoint()).isEqualTo('b'); + assertThat(scanner.peek()).isEqualTo('\n'); scanner.next(); - assertTrue(scanner.hasNext()); - assertEquals('\n', scanner.peekPreviousCodePoint()); - assertEquals('c', scanner.peek()); + assertThat(scanner.hasNext()).isTrue(); + assertThat(scanner.peekPreviousCodePoint()).isEqualTo('\n'); + assertThat(scanner.peek()).isEqualTo('c'); scanner.next(); - assertTrue(scanner.hasNext()); - assertEquals('c', scanner.peekPreviousCodePoint()); - assertEquals('d', scanner.peek()); + assertThat(scanner.hasNext()).isTrue(); + assertThat(scanner.peekPreviousCodePoint()).isEqualTo('c'); + assertThat(scanner.peek()).isEqualTo('d'); scanner.next(); - assertTrue(scanner.hasNext()); - assertEquals('d', scanner.peekPreviousCodePoint()); - assertEquals('e', scanner.peek()); + assertThat(scanner.hasNext()).isTrue(); + assertThat(scanner.peekPreviousCodePoint()).isEqualTo('d'); + assertThat(scanner.peek()).isEqualTo('e'); scanner.next(); - assertFalse(scanner.hasNext()); - assertEquals('e', scanner.peekPreviousCodePoint()); - assertEquals('\0', scanner.peek()); + assertThat(scanner.hasNext()).isFalse(); + assertThat(scanner.peekPreviousCodePoint()).isEqualTo('e'); + assertThat(scanner.peek()).isEqualTo('\0'); } @Test - public void testCodePoints() { + void testCodePoints() { Scanner scanner = new Scanner(List.of(SourceLine.of("\uD83D\uDE0A", null)), 0, 0); - assertTrue(scanner.hasNext()); - assertEquals('\0', scanner.peekPreviousCodePoint()); - assertEquals(128522, scanner.peekCodePoint()); + assertThat(scanner.hasNext()).isTrue(); + assertThat(scanner.peekPreviousCodePoint()).isEqualTo('\0'); + assertThat(scanner.peekCodePoint()).isEqualTo(128522); scanner.next(); // This jumps chars, not code points. So jump two here scanner.next(); - assertFalse(scanner.hasNext()); - assertEquals(128522, scanner.peekPreviousCodePoint()); - assertEquals('\0', scanner.peekCodePoint()); + assertThat(scanner.hasNext()).isFalse(); + assertThat(scanner.peekPreviousCodePoint()).isEqualTo(128522); + assertThat(scanner.peekCodePoint()).isEqualTo('\0'); } @Test - public void testTextBetween() { + void testTextBetween() { Scanner scanner = new Scanner(List.of( SourceLine.of("ab", SourceSpan.of(10, 3, 13, 2)), SourceLine.of("cde", SourceSpan.of(11, 4, 20, 3))), @@ -139,20 +139,20 @@ public void testTextBetween() { } private void assertSourceLines(SourceLines sourceLines, String expectedContent, SourceSpan... expectedSourceSpans) { - assertEquals(expectedContent, sourceLines.getContent()); - assertEquals(List.of(expectedSourceSpans), sourceLines.getSourceSpans()); + assertThat(sourceLines.getContent()).isEqualTo(expectedContent); + assertThat(sourceLines.getSourceSpans()).isEqualTo(List.of(expectedSourceSpans)); } @Test - public void nextString() { + void nextString() { Scanner scanner = Scanner.of(SourceLines.of(List.of( SourceLine.of("hey ya", null), SourceLine.of("hi", null)))); - assertFalse(scanner.next("hoy")); - assertTrue(scanner.next("hey")); - assertTrue(scanner.next(' ')); - assertFalse(scanner.next("yo")); - assertTrue(scanner.next("ya")); - assertFalse(scanner.next(" ")); + assertThat(scanner.next("hoy")).isFalse(); + assertThat(scanner.next("hey")).isTrue(); + assertThat(scanner.next(' ')).isTrue(); + assertThat(scanner.next("yo")).isFalse(); + assertThat(scanner.next("ya")).isTrue(); + assertThat(scanner.next(" ")).isFalse(); } } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 36a5b528c..36b8adad9 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -3,15 +3,12 @@ import org.commonmark.node.*; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlNodeRendererFactory; -import org.commonmark.renderer.html.HtmlRenderer; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Set; +import static org.assertj.core.api.Assertions.assertThat; import static org.commonmark.testutil.Asserts.assertRendering; -import static org.junit.Assert.assertEquals; public class MarkdownRendererTest { @@ -31,7 +28,7 @@ public void testThematicBreaks() { // Apply fallback for null literal ThematicBreak node = new ThematicBreak(); - assertEquals("___", render(node)); + assertThat(render(node)).isEqualTo("___"); } @Test @@ -255,7 +252,7 @@ public void testEmphasis() { Emphasis e2 = new Emphasis(); e1.appendChild(e2); e2.appendChild(new Text("hi")); - assertEquals("*_hi_*\n", render(doc)); + assertThat(render(doc)).isEqualTo("*_hi_*\n"); } @Test @@ -332,12 +329,12 @@ public Set getSpecialCharacters() { MarkdownRenderer renderer = MarkdownRenderer.builder().nodeRendererFactory(nodeRendererFactory).build(); String rendered = renderer.render(parse("# Hello")); - assertEquals("# Custom heading\n", rendered); + assertThat(rendered).isEqualTo("# Custom heading\n"); } private void assertRoundTrip(String input) { String rendered = parseAndRender(input); - assertEquals(input, rendered); + assertThat(rendered).isEqualTo(input); } private String parseAndRender(String source) { diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 5df2e5c80..3b88df55d 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -6,15 +6,14 @@ import org.commonmark.testutil.TestResources; import org.commonmark.testutil.example.Example; import org.commonmark.testutil.example.ExampleReader; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests Markdown rendering using the examples in the spec like this: @@ -63,9 +62,8 @@ public void testCoverage() { System.out.println(); } - int expectedPassed = 652; - assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); - assertEquals(0, fails.size()); + assertThat(passes).hasSizeGreaterThanOrEqualTo(652); + assertThat(fails).isEmpty(); } private static void printCountsBySection(List examples) { diff --git a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java index b3b60fa3b..edb6936f4 100644 --- a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java @@ -1,10 +1,9 @@ package org.commonmark.test; import org.commonmark.node.*; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public class AbstractVisitorTest { @@ -26,13 +25,13 @@ public void visit(Text text) { assertCode("foo", paragraph.getFirstChild()); assertCode("bar", paragraph.getFirstChild().getNext()); - assertNull(paragraph.getFirstChild().getNext().getNext()); + assertThat(paragraph.getFirstChild().getNext().getNext()).isNull(); assertCode("bar", paragraph.getLastChild()); } private static void assertCode(String expectedLiteral, Node node) { - assertEquals("Expected node to be a Code node: " + node, Code.class, node.getClass()); + assertThat(node).isInstanceOf(Code.class); Code code = (Code) node; - assertEquals(expectedLiteral, code.getLiteral()); + assertThat(code.getLiteral()).isEqualTo(expectedLiteral); } } diff --git a/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java b/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java index a34a32c44..3f2f0d611 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java @@ -2,12 +2,12 @@ import org.commonmark.node.*; import org.commonmark.parser.Parser; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class DelimitedTest { @@ -35,20 +35,20 @@ public void visit(StrongEmphasis node) { }; document.accept(visitor); - assertEquals(4, list.size()); + assertThat(list).hasSize(4); Delimited emphasis = list.get(0); Delimited strong = list.get(1); Delimited important = list.get(2); Delimited critical = list.get(3); - assertEquals("*", emphasis.getOpeningDelimiter()); - assertEquals("*", emphasis.getClosingDelimiter()); - assertEquals("**", strong.getOpeningDelimiter()); - assertEquals("**", strong.getClosingDelimiter()); - assertEquals("_", important.getOpeningDelimiter()); - assertEquals("_", important.getClosingDelimiter()); - assertEquals("__", critical.getOpeningDelimiter()); - assertEquals("__", critical.getClosingDelimiter()); + assertThat(emphasis.getOpeningDelimiter()).isEqualTo("*"); + assertThat(emphasis.getClosingDelimiter()).isEqualTo("*"); + assertThat(strong.getOpeningDelimiter()).isEqualTo("**"); + assertThat(strong.getClosingDelimiter()).isEqualTo("**"); + assertThat(important.getOpeningDelimiter()).isEqualTo("_"); + assertThat(important.getClosingDelimiter()).isEqualTo("_"); + assertThat(critical.getOpeningDelimiter()).isEqualTo("__"); + assertThat(critical.getClosingDelimiter()).isEqualTo("__"); } } diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index 680c40bf2..e4920120d 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -11,12 +11,13 @@ import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Locale; import java.util.Set; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class DelimiterProcessorTest extends RenderingTestCase { @@ -29,8 +30,8 @@ public void delimiterProcessorWithInvalidDelimiterUse() { .customDelimiterProcessor(new CustomDelimiterProcessor(':', 0)) .customDelimiterProcessor(new CustomDelimiterProcessor(';', -1)) .build(); - assertEquals("

    :test:

    \n", RENDERER.render(parser.parse(":test:"))); - assertEquals("

    ;test;

    \n", RENDERER.render(parser.parse(";test;"))); + assertThat(RENDERER.render(parser.parse(":test:"))).isEqualTo("

    :test:

    \n"); + assertThat(RENDERER.render(parser.parse(";test;"))).isEqualTo("

    ;test;

    \n"); } @Test @@ -54,16 +55,17 @@ public void multipleDelimitersWithDifferentLengths() { .customDelimiterProcessor(new OneDelimiterProcessor()) .customDelimiterProcessor(new TwoDelimiterProcessor()) .build(); - assertEquals("

    (1)one(/1) (2)two(/2)

    \n", RENDERER.render(parser.parse("+one+ ++two++"))); - assertEquals("

    (1)(2)both(/2)(/1)

    \n", RENDERER.render(parser.parse("+++both+++"))); + assertThat(RENDERER.render(parser.parse("+one+ ++two++"))).isEqualTo("

    (1)one(/1) (2)two(/2)

    \n"); + assertThat(RENDERER.render(parser.parse("+++both+++"))).isEqualTo("

    (1)(2)both(/2)(/1)

    \n"); } - @Test(expected = IllegalArgumentException.class) + @Test public void multipleDelimitersWithSameLengthConflict() { - Parser.builder() - .customDelimiterProcessor(new OneDelimiterProcessor()) - .customDelimiterProcessor(new OneDelimiterProcessor()) - .build(); + assertThatThrownBy(() -> + Parser.builder() + .customDelimiterProcessor(new OneDelimiterProcessor()) + .customDelimiterProcessor(new OneDelimiterProcessor()) + .build()).isInstanceOf(IllegalArgumentException.class); } @Override diff --git a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java index 774c6ff0e..443b0fa51 100644 --- a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java @@ -5,9 +5,9 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class FencedCodeBlockParserTest extends RenderingTestCase { @@ -18,8 +18,8 @@ public class FencedCodeBlockParserTest extends RenderingTestCase { public void backtickInfo() { Node document = PARSER.parse("```info ~ test\ncode\n```"); FencedCodeBlock codeBlock = (FencedCodeBlock) document.getFirstChild(); - assertEquals("info ~ test", codeBlock.getInfo()); - assertEquals("code\n", codeBlock.getLiteral()); + assertThat(codeBlock.getInfo()).isEqualTo("info ~ test"); + assertThat(codeBlock.getLiteral()).isEqualTo("code\n"); } @Test diff --git a/commonmark/src/test/java/org/commonmark/test/HeadingParserTest.java b/commonmark/src/test/java/org/commonmark/test/HeadingParserTest.java index a5b179a81..f7bf35a4c 100644 --- a/commonmark/src/test/java/org/commonmark/test/HeadingParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HeadingParserTest.java @@ -3,7 +3,7 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class HeadingParserTest extends RenderingTestCase { diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java index 965a2f181..8e1fd9790 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java @@ -1,6 +1,6 @@ package org.commonmark.test; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class HtmlInlineParserTest extends CoreRenderingTestCase { diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 8e5a8f30d..413bb9d8a 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -5,59 +5,54 @@ import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.*; import org.commonmark.testutil.TestResources; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class HtmlRendererTest { @Test public void htmlAllowingShouldNotEscapeInlineHtml() { String rendered = htmlAllowingRenderer().render(parse("paragraph with inline & html")); - assertEquals("

    paragraph with inline & html

    \n", rendered); + assertThat(rendered).isEqualTo("

    paragraph with inline & html

    \n"); } @Test public void htmlAllowingShouldNotEscapeBlockHtml() { String rendered = htmlAllowingRenderer().render(parse("
    block &
    ")); - assertEquals("
    block &
    \n", rendered); + assertThat(rendered).isEqualTo("
    block &
    \n"); } @Test public void htmlEscapingShouldEscapeInlineHtml() { String rendered = htmlEscapingRenderer().render(parse("paragraph with inline & html")); // Note that & is not escaped, as it's a normal text node, not part of the inline HTML. - assertEquals("

    paragraph with <span id='foo' class="bar">inline & html</span>

    \n", rendered); + assertThat(rendered).isEqualTo("

    paragraph with <span id='foo' class="bar">inline & html</span>

    \n"); } @Test public void htmlEscapingShouldEscapeHtmlBlocks() { String rendered = htmlEscapingRenderer().render(parse("
    block &
    ")); - assertEquals("

    <div id='foo' class="bar">block &amp;</div>

    \n", rendered); + assertThat(rendered).isEqualTo("

    <div id='foo' class="bar">block &amp;</div>

    \n"); } @Test public void textEscaping() { String rendered = defaultRenderer().render(parse("escaping: & < > \" '")); - assertEquals("

    escaping: & < > " '

    \n", rendered); + assertThat(rendered).isEqualTo("

    escaping: & < > " '

    \n"); } @Test public void characterReferencesWithoutSemicolonsShouldNotBeParsedShouldBeEscaped() { String input = "[example](javascript:alert('XSS'))"; String rendered = defaultRenderer().render(parse(input)); - assertEquals("

    example

    \n", rendered); + assertThat(rendered).isEqualTo("

    example

    \n"); } @Test @@ -66,7 +61,7 @@ public void attributeEscaping() { Link link = new Link(); link.setDestination(":"); paragraph.appendChild(link); - assertEquals("

    \n", defaultRenderer().render(paragraph)); + assertThat(defaultRenderer().render(paragraph)).isEqualTo("

    \n"); } @Test @@ -75,7 +70,7 @@ public void rawUrlsShouldNotFilterDangerousProtocols() { Link link = new Link(); link.setDestination("javascript:alert(5);"); paragraph.appendChild(link); - assertEquals("

    \n", rawUrlsRenderer().render(paragraph)); + assertThat(rawUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); } @Test @@ -84,13 +79,13 @@ public void sanitizedUrlsShouldSetRelNoFollow() { Link link = new Link(); link.setDestination("/exampleUrl"); paragraph.appendChild(link); - assertEquals("

    \n", sanitizeUrlsRenderer().render(paragraph)); + assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); paragraph = new Paragraph(); link = new Link(); link.setDestination("https://google.com"); paragraph.appendChild(link); - assertEquals("

    \n", sanitizeUrlsRenderer().render(paragraph)); + assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); } @Test @@ -99,26 +94,26 @@ public void sanitizedUrlsShouldAllowSafeProtocols() { Link link = new Link(); link.setDestination("http://google.com"); paragraph.appendChild(link); - assertEquals("

    \n", sanitizeUrlsRenderer().render(paragraph)); + assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); paragraph = new Paragraph(); link = new Link(); link.setDestination("https://google.com"); paragraph.appendChild(link); - assertEquals("

    \n", sanitizeUrlsRenderer().render(paragraph)); + assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); paragraph = new Paragraph(); link = new Link(); link.setDestination("mailto:foo@bar.example.com"); paragraph.appendChild(link); - assertEquals("

    \n", sanitizeUrlsRenderer().render(paragraph)); + assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); String image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAAAAQSURBVBhXY/iPBVBf8P9/AG8TY51nJdgkAAAAAElFTkSuQmCC"; paragraph = new Paragraph(); link = new Link(); link.setDestination(image); paragraph.appendChild(link); - assertEquals("

    \n", sanitizeUrlsRenderer().render(paragraph)); + assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); } @Test @@ -127,45 +122,42 @@ public void sanitizedUrlsShouldFilterDangerousProtocols() { Link link = new Link(); link.setDestination("javascript:alert(5);"); paragraph.appendChild(link); - assertEquals("

    \n", sanitizeUrlsRenderer().render(paragraph)); + assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); paragraph = new Paragraph(); link = new Link(); link.setDestination("ftp://google.com"); paragraph.appendChild(link); - assertEquals("

    \n", sanitizeUrlsRenderer().render(paragraph)); + assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); } @Test public void percentEncodeUrlDisabled() { - assertEquals("

    a

    \n", defaultRenderer().render(parse("[a](foo&bar)"))); - assertEquals("

    a

    \n", defaultRenderer().render(parse("[a](ä)"))); - assertEquals("

    a

    \n", defaultRenderer().render(parse("[a](foo%20bar)"))); + assertThat(defaultRenderer().render(parse("[a](foo&bar)"))).isEqualTo("

    a

    \n"); + assertThat(defaultRenderer().render(parse("[a](ä)"))).isEqualTo("

    a

    \n"); + assertThat(defaultRenderer().render(parse("[a](foo%20bar)"))).isEqualTo("

    a

    \n"); } @Test public void percentEncodeUrl() { // Entities are escaped anyway - assertEquals("

    a

    \n", percentEncodingRenderer().render(parse("[a](foo&bar)"))); + assertThat(percentEncodingRenderer().render(parse("[a](foo&bar)"))).isEqualTo("

    a

    \n"); // Existing encoding is preserved - assertEquals("

    a

    \n", percentEncodingRenderer().render(parse("[a](foo%20bar)"))); - assertEquals("

    a

    \n", percentEncodingRenderer().render(parse("[a](foo%61)"))); + assertThat(percentEncodingRenderer().render(parse("[a](foo%20bar)"))).isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](foo%61)"))).isEqualTo("

    a

    \n"); // Invalid encoding is escaped - assertEquals("

    a

    \n", percentEncodingRenderer().render(parse("[a](foo%)"))); - assertEquals("

    a

    \n", percentEncodingRenderer().render(parse("[a](foo%a)"))); - assertEquals("

    a

    \n", percentEncodingRenderer().render(parse("[a](foo%a_)"))); - assertEquals("

    a

    \n", percentEncodingRenderer().render(parse("[a](foo%xx)"))); + assertThat(percentEncodingRenderer().render(parse("[a](foo%)"))).isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](foo%a)"))).isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](foo%a_)"))).isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](foo%xx)"))).isEqualTo("

    a

    \n"); // Reserved characters are preserved, except for '[' and ']' - assertEquals("

    a

    \n", percentEncodingRenderer().render(parse("[a](!*'();:@&=+$,/?#[])"))); + assertThat(percentEncodingRenderer().render(parse("[a](!*'();:@&=+$,/?#[])"))).isEqualTo("

    a

    \n"); // Unreserved characters are preserved - assertEquals("

    a

    \n", - percentEncodingRenderer().render(parse("[a](ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~)"))); + assertThat(percentEncodingRenderer().render(parse("[a](ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~)"))).isEqualTo("

    a

    \n"); // Other characters are percent-encoded (LATIN SMALL LETTER A WITH DIAERESIS) - assertEquals("

    a

    \n", - percentEncodingRenderer().render(parse("[a](ä)"))); + assertThat(percentEncodingRenderer().render(parse("[a](ä)"))).isEqualTo("

    a

    \n"); // Other characters are percent-encoded (MUSICAL SYMBOL G CLEF, surrogate pair in UTF-16) - assertEquals("

    a

    \n", - percentEncodingRenderer().render(parse("[a](\uD834\uDD1E)"))); + assertThat(percentEncodingRenderer().render(parse("[a](\uD834\uDD1E)"))).isEqualTo("

    a

    \n"); } @Test @@ -192,10 +184,10 @@ public void setAttributes(Node node, String tagName, Map attribu HtmlRenderer renderer = HtmlRenderer.builder().attributeProviderFactory(custom).build(); String rendered = renderer.render(parse("```info\ncontent\n```")); - assertEquals("
    content\n
    \n", rendered); + assertThat(rendered).isEqualTo("
    content\n
    \n"); String rendered2 = renderer.render(parse("```evil\"\ncontent\n```")); - assertEquals("
    content\n
    \n", rendered2); + assertThat(rendered2).isEqualTo("
    content\n
    \n"); } @Test @@ -217,7 +209,7 @@ public void setAttributes(Node node, String tagName, Map attribu HtmlRenderer renderer = HtmlRenderer.builder().attributeProviderFactory(custom).build(); String rendered = renderer.render(parse("![foo](/url)\n")); - assertEquals("

    \n", rendered); + assertThat(rendered).isEqualTo("

    \n"); } @Test @@ -240,7 +232,7 @@ public void setAttributes(Node node, String tagName, Map attribu HtmlRenderer renderer = HtmlRenderer.builder().attributeProviderFactory(factory).build(); String rendered = renderer.render(parse("text node")); String secondPass = renderer.render(parse("text node")); - assertEquals(rendered, secondPass); + assertThat(secondPass).isEqualTo(rendered); } @Test @@ -264,30 +256,27 @@ public void render(Node node) { HtmlRenderer renderer = HtmlRenderer.builder().nodeRendererFactory(nodeRendererFactory).build(); String rendered = renderer.render(parse("foo [bar](/url)")); - assertEquals("

    foo test

    \n", rendered); + assertThat(rendered).isEqualTo("

    foo test

    \n"); } @Test public void orderedListStartZero() { - assertEquals("
      \n
    1. Test
    2. \n
    \n", defaultRenderer().render(parse("0. Test\n"))); + assertThat(defaultRenderer().render(parse("0. Test\n"))).isEqualTo("
      \n
    1. Test
    2. \n
    \n"); } @Test public void imageAltTextWithSoftLineBreak() { - assertEquals("

    \"foo\nbar\"

    \n", - defaultRenderer().render(parse("![foo\nbar](/url)\n"))); + assertThat(defaultRenderer().render(parse("![foo\nbar](/url)\n"))).isEqualTo("

    \"foo\nbar\"

    \n"); } @Test public void imageAltTextWithHardLineBreak() { - assertEquals("

    \"foo\nbar\"

    \n", - defaultRenderer().render(parse("![foo \nbar](/url)\n"))); + assertThat(defaultRenderer().render(parse("![foo \nbar](/url)\n"))).isEqualTo("

    \"foo\nbar\"

    \n"); } @Test public void imageAltTextWithEntities() { - assertEquals("

    \"foo

    \n", - defaultRenderer().render(parse("![foo ä](/url)\n"))); + assertThat(defaultRenderer().render(parse("![foo ä](/url)\n"))).isEqualTo("

    \"foo

    \n"); } @Test @@ -304,41 +293,35 @@ public void canRenderContentsOfSingleParagraph() { document.appendChild(current); } - assertEquals("Here I have a test link", - defaultRenderer().render(document)); + assertThat(defaultRenderer().render(document)).isEqualTo("Here I have a test link"); } @Test public void omitSingleParagraphP() { var renderer = HtmlRenderer.builder().omitSingleParagraphP(true).build(); - assertEquals("hi there", renderer.render(parse("hi *there*"))); + assertThat(renderer.render(parse("hi *there*"))).isEqualTo("hi there"); } @Test public void threading() throws Exception { - Parser parser = Parser.builder().build(); - String spec = TestResources.readAsString(TestResources.getSpec()); - final Node document = parser.parse(spec); + var parser = Parser.builder().build(); + var spec = TestResources.readAsString(TestResources.getSpec()); + var document = parser.parse(spec); - final HtmlRenderer htmlRenderer = HtmlRenderer.builder().build(); - String expectedRendering = htmlRenderer.render(document); + var htmlRenderer = HtmlRenderer.builder().build(); + var expectedRendering = htmlRenderer.render(document); // Render in parallel using the same HtmlRenderer instance. - List> futures = new ArrayList<>(); - ExecutorService executorService = Executors.newFixedThreadPool(4); + var futures = new ArrayList>(); + var executorService = Executors.newFixedThreadPool(4); for (int i = 0; i < 40; i++) { - Future future = executorService.submit(new Callable() { - @Override - public String call() throws Exception { - return htmlRenderer.render(document); - } - }); + var future = executorService.submit(() -> htmlRenderer.render(document)); futures.add(future); } - for (Future future : futures) { - String rendering = future.get(); - assertThat(rendering, is(expectedRendering)); + for (var future : futures) { + var rendering = future.get(); + assertThat(rendering).isEqualTo(expectedRendering); } } diff --git a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java index 2f2463ccb..c05cac2d2 100644 --- a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java +++ b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java @@ -10,13 +10,13 @@ import org.commonmark.parser.Parser; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.renderer.html.HtmlRenderer; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; import java.util.Set; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class InlineParserContextTest { @@ -30,10 +30,10 @@ public void labelShouldBeOriginalNotNormalized() { String rendered = HtmlRenderer.builder().build().render(parser.parse(input)); // Lookup should pass original label to context - assertEquals(List.of("FooBarBaz"), inlineParserFactory.lookups); + assertThat(inlineParserFactory.lookups).isEqualTo(List.of("FooBarBaz")); // Context should normalize label for finding reference - assertEquals("

    link with special label

    \n", rendered); + assertThat(rendered).isEqualTo("

    link with special label

    \n"); } static class CapturingInlineParserFactory implements InlineParserFactory { diff --git a/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java b/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java index 81a71ee69..8410ff028 100644 --- a/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java +++ b/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java @@ -2,13 +2,11 @@ import org.commonmark.node.*; import org.commonmark.parser.Parser; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.List; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; public class LinkReferenceDefinitionNodeTest { @@ -17,12 +15,12 @@ public void testDefinitionWithoutParagraph() { Node document = parse("This is a paragraph with a [foo] link.\n\n[foo]: /url 'title'"); List nodes = Nodes.getChildren(document); - assertThat(nodes.size(), is(2)); - assertThat(nodes.get(0), instanceOf(Paragraph.class)); + assertThat(nodes).hasSize(2); + assertThat(nodes.get(0)).isInstanceOf(Paragraph.class); LinkReferenceDefinition definition = assertDef(nodes.get(1), "foo"); - assertThat(definition.getDestination(), is("/url")); - assertThat(definition.getTitle(), is("title")); + assertThat(definition.getDestination()).isEqualTo("/url"); + assertThat(definition.getTitle()).isEqualTo("title"); } @Test @@ -30,10 +28,10 @@ public void testDefinitionWithParagraph() { Node document = parse("[foo]: /url\nThis is a paragraph with a [foo] link."); List nodes = Nodes.getChildren(document); - assertThat(nodes.size(), is(2)); + assertThat(nodes).hasSize(2); // Note that definition is not part of the paragraph, it's a sibling - assertThat(nodes.get(0), instanceOf(LinkReferenceDefinition.class)); - assertThat(nodes.get(1), instanceOf(Paragraph.class)); + assertThat(nodes.get(0)).isInstanceOf(LinkReferenceDefinition.class); + assertThat(nodes.get(1)).isInstanceOf(Paragraph.class); } @Test @@ -41,8 +39,8 @@ public void testMultipleDefinitions() { Node document = parse("This is a paragraph with a [foo] link.\n\n[foo]: /url\n[bar]: /url"); List nodes = Nodes.getChildren(document); - assertThat(nodes.size(), is(3)); - assertThat(nodes.get(0), instanceOf(Paragraph.class)); + assertThat(nodes).hasSize(3); + assertThat(nodes.get(0)).isInstanceOf(Paragraph.class); assertDef(nodes.get(1), "foo"); assertDef(nodes.get(2), "bar"); } @@ -52,14 +50,14 @@ public void testMultipleDefinitionsWithSameLabel() { Node document = parse("This is a paragraph with a [foo] link.\n\n[foo]: /url1\n[foo]: /url2"); List nodes = Nodes.getChildren(document); - assertThat(nodes.size(), is(3)); - assertThat(nodes.get(0), instanceOf(Paragraph.class)); + assertThat(nodes).hasSize(3); + assertThat(nodes.get(0)).isInstanceOf(Paragraph.class); LinkReferenceDefinition def1 = assertDef(nodes.get(1), "foo"); - assertThat(def1.getDestination(), is("/url1")); + assertThat(def1.getDestination()).isEqualTo("/url1"); // When there's multiple definitions with the same label, the first one "wins", as in reference links will use // that. But we still want to preserve the original definitions in the document. LinkReferenceDefinition def2 = assertDef(nodes.get(2), "foo"); - assertThat(def2.getDestination(), is("/url2")); + assertThat(def2.getDestination()).isEqualTo("/url2"); } @Test @@ -67,42 +65,42 @@ public void testDefinitionOfReplacedBlock() { Node document = parse("[foo]: /url\nHeading\n======="); List nodes = Nodes.getChildren(document); - assertThat(nodes.size(), is(2)); + assertThat(nodes).hasSize(2); assertDef(nodes.get(0), "foo"); - assertThat(nodes.get(1), instanceOf(Heading.class)); + assertThat(nodes.get(1)).isInstanceOf(Heading.class); } @Test public void testDefinitionInListItem() { Node document = parse("* [foo]: /url\n [foo]\n"); - assertThat(document.getFirstChild(), instanceOf(BulletList.class)); + assertThat(document.getFirstChild()).isInstanceOf(BulletList.class); Node item = document.getFirstChild().getFirstChild(); - assertThat(item, instanceOf(ListItem.class)); + assertThat(item).isInstanceOf(ListItem.class); List nodes = Nodes.getChildren(item); - assertThat(nodes.size(), is(2)); + assertThat(nodes).hasSize(2); assertDef(nodes.get(0), "foo"); - assertThat(nodes.get(1), instanceOf(Paragraph.class)); + assertThat(nodes.get(1)).isInstanceOf(Paragraph.class); } @Test public void testDefinitionInListItem2() { Node document = parse("* [foo]: /url\n* [foo]\n"); - assertThat(document.getFirstChild(), instanceOf(BulletList.class)); + assertThat(document.getFirstChild()).isInstanceOf(BulletList.class); List items = Nodes.getChildren(document.getFirstChild()); - assertThat(items.size(), is(2)); + assertThat(items).hasSize(2); Node item1 = items.get(0); Node item2 = items.get(1); - assertThat(item1, instanceOf(ListItem.class)); - assertThat(item2, instanceOf(ListItem.class)); + assertThat(item1).isInstanceOf(ListItem.class); + assertThat(item2).isInstanceOf(ListItem.class); - assertThat(Nodes.getChildren(item1).size(), is(1)); + assertThat(Nodes.getChildren(item1)).hasSize(1); assertDef(item1.getFirstChild(), "foo"); - assertThat(Nodes.getChildren(item2).size(), is(1)); - assertThat(item2.getFirstChild(), instanceOf(Paragraph.class)); + assertThat(Nodes.getChildren(item2)).hasSize(1); + assertThat(item2.getFirstChild()).isInstanceOf(Paragraph.class); } @Test @@ -110,8 +108,8 @@ public void testDefinitionLabelCaseIsPreserved() { Node document = parse("This is a paragraph with a [foo] link.\n\n[fOo]: /url 'title'"); List nodes = Nodes.getChildren(document); - assertThat(nodes.size(), is(2)); - assertThat(nodes.get(0), instanceOf(Paragraph.class)); + assertThat(nodes).hasSize(2); + assertThat(nodes.get(0)).isInstanceOf(Paragraph.class); assertDef(nodes.get(1), "fOo"); } @@ -121,9 +119,9 @@ private static Node parse(String input) { } private static LinkReferenceDefinition assertDef(Node node, String label) { - assertThat(node, instanceOf(LinkReferenceDefinition.class)); + assertThat(node).isInstanceOf(LinkReferenceDefinition.class); LinkReferenceDefinition def = (LinkReferenceDefinition) node; - assertThat(def.getLabel(), is(label)); + assertThat(def.getLabel()).isEqualTo(label); return def; } } diff --git a/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java b/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java index 4a38bc412..02ac3abff 100644 --- a/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java @@ -3,9 +3,9 @@ import org.commonmark.node.ListItem; import org.commonmark.node.Node; import org.commonmark.parser.Parser; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class ListBlockParserTest { @@ -59,7 +59,7 @@ public void testOrderedListIndents() { private void assertListItemIndents(String input, int expectedMarkerIndent, int expectedContentIndent) { Node doc = PARSER.parse(input); ListItem listItem = Nodes.find(doc, ListItem.class); - assertEquals(expectedMarkerIndent, (int) listItem.getMarkerIndent()); - assertEquals(expectedContentIndent, (int) listItem.getContentIndent()); + assertThat((int) listItem.getMarkerIndent()).isEqualTo(expectedMarkerIndent); + assertThat((int) listItem.getContentIndent()).isEqualTo(expectedContentIndent); } } diff --git a/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java b/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java index 4889bb9ab..c6bda31ed 100644 --- a/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java @@ -1,6 +1,6 @@ package org.commonmark.test; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class ListTightLooseTest extends CoreRenderingTestCase { diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 447a50c3d..3b9ef09e9 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -5,7 +5,7 @@ import org.commonmark.parser.block.*; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.TestResources; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.InputStream; @@ -20,9 +20,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class ParserTest { @@ -40,7 +39,7 @@ public void ioReaderTest() throws IOException { Node document2 = parser.parse(spec); HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build(); - assertEquals(renderer.render(document2), renderer.render(document1)); + assertThat(renderer.render(document1)).isEqualTo(renderer.render(document2)); } @Test @@ -50,9 +49,9 @@ public void customBlockParserFactory() { // The dashes would normally be a ThematicBreak Node document = parser.parse("hey\n\n---\n"); - assertThat(document.getFirstChild(), instanceOf(Paragraph.class)); - assertEquals("hey", ((Text) document.getFirstChild().getFirstChild()).getLiteral()); - assertThat(document.getLastChild(), instanceOf(DashBlock.class)); + assertThat(document.getFirstChild()).isInstanceOf(Paragraph.class); + assertThat(((Text) document.getFirstChild().getFirstChild()).getLiteral()).isEqualTo("hey"); + assertThat(document.getLastChild()).isInstanceOf(DashBlock.class); } @Test @@ -61,24 +60,25 @@ public void enabledBlockTypes() { Parser parser = Parser.builder().build(); // all core parsers by default Node document = parser.parse(given); - assertThat(document.getFirstChild(), instanceOf(Heading.class)); + assertThat(document.getFirstChild()).isInstanceOf(Heading.class); Set> headersOnly = new HashSet<>(); headersOnly.add(Heading.class); parser = Parser.builder().enabledBlockTypes(headersOnly).build(); document = parser.parse(given); - assertThat(document.getFirstChild(), instanceOf(Heading.class)); + assertThat(document.getFirstChild()).isInstanceOf(Heading.class); Set> noCoreTypes = new HashSet<>(); parser = Parser.builder().enabledBlockTypes(noCoreTypes).build(); document = parser.parse(given); - assertThat(document.getFirstChild(), not(instanceOf(Heading.class))); + assertThat(document.getFirstChild()).isNotInstanceOf(Heading.class); } - @Test(expected = IllegalArgumentException.class) + @Test public void enabledBlockTypesThrowsWhenGivenUnknownClass() { // BulletList can't be enabled separately at the moment, only all ListBlock types - Parser.builder().enabledBlockTypes(Set.of(Heading.class, BulletList.class)).build(); + assertThatThrownBy(() -> + Parser.builder().enabledBlockTypes(Set.of(Heading.class, BulletList.class)).build()).isInstanceOf(IllegalArgumentException.class); } @Test @@ -87,19 +87,19 @@ public void indentation() { Parser parser = Parser.builder().build(); Node document = parser.parse(given); - assertThat(document.getFirstChild(), instanceOf(BulletList.class)); + assertThat(document.getFirstChild()).isInstanceOf(BulletList.class); Node list = document.getFirstChild(); // first level list - assertEquals("expect one child", list.getFirstChild(), list.getLastChild()); - assertEquals("1 space", firstText(list.getFirstChild())); + assertThat(list.getLastChild()).as("expect one child").isEqualTo(list.getFirstChild()); + assertThat(firstText(list.getFirstChild())).isEqualTo("1 space"); list = list.getFirstChild().getLastChild(); // second level list - assertEquals("expect one child", list.getFirstChild(), list.getLastChild()); - assertEquals("3 spaces", firstText(list.getFirstChild())); + assertThat(list.getLastChild()).as("expect one child").isEqualTo(list.getFirstChild()); + assertThat(firstText(list.getFirstChild())).isEqualTo("3 spaces"); list = list.getFirstChild().getLastChild(); // third level list - assertEquals("5 spaces", firstText(list.getFirstChild())); - assertEquals("tab + space", firstText(list.getFirstChild().getNext())); + assertThat(firstText(list.getFirstChild())).isEqualTo("5 spaces"); + assertThat(firstText(list.getFirstChild().getNext())).isEqualTo("tab + space"); } @Test @@ -122,39 +122,34 @@ public InlineParser create(InlineParserContext inlineParserContext) { Parser parser = Parser.builder().inlineParserFactory(fakeInlineParserFactory).build(); String input = "**bold** **bold** ~~strikethrough~~"; - assertThat(parser.parse(input).getFirstChild().getFirstChild(), instanceOf(ThematicBreak.class)); + assertThat(parser.parse(input).getFirstChild().getFirstChild()).isInstanceOf(ThematicBreak.class); } @Test public void threading() throws Exception { - final Parser parser = Parser.builder().build(); - final String spec = TestResources.readAsString(TestResources.getSpec()); + var parser = Parser.builder().build(); + var spec = TestResources.readAsString(TestResources.getSpec()); - HtmlRenderer renderer = HtmlRenderer.builder().build(); - String expectedRendering = renderer.render(parser.parse(spec)); + var renderer = HtmlRenderer.builder().build(); + var expectedRendering = renderer.render(parser.parse(spec)); // Parse in parallel using the same Parser instance. - List> futures = new ArrayList<>(); - ExecutorService executorService = Executors.newFixedThreadPool(4); + var futures = new ArrayList>(); + var executorService = Executors.newFixedThreadPool(4); for (int i = 0; i < 40; i++) { - Future future = executorService.submit(new Callable() { - @Override - public Node call() throws Exception { - return parser.parse(spec); - } - }); + var future = executorService.submit(() -> parser.parse(spec)); futures.add(future); } - for (Future future : futures) { - Node node = future.get(); - assertThat(renderer.render(node), is(expectedRendering)); + for (var future : futures) { + var node = future.get(); + assertThat(renderer.render(node)).isEqualTo(expectedRendering); } } private String firstText(Node n) { while (!(n instanceof Text)) { - assertThat(n, notNullValue()); + assertThat(n).isNotNull(); n = n.getFirstChild(); } return ((Text) n).getLiteral(); diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index ae1310ed2..66d39de23 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -1,34 +1,21 @@ package org.commonmark.test; -import org.junit.FixMethodOrder; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.Stopwatch; -import org.junit.rules.Timeout; -import org.junit.runner.Description; -import org.junit.runners.MethodSorters; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.Timeout; import java.util.concurrent.TimeUnit; /** * Pathological input cases (from commonmark.js). */ -@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Timeout(value = 3, unit = TimeUnit.SECONDS) +@TestMethodOrder(MethodOrderer.MethodName.class) public class PathologicalTest extends CoreRenderingTestCase { private int x = 100_000; - @Rule - public Timeout timeout = new Timeout(3, TimeUnit.SECONDS); - - @Rule - public Stopwatch stopwatch = new Stopwatch() { - @Override - protected void finished(long nanos, Description description) { - System.err.println(description.getDisplayName() + " took " + (nanos / 1000000) + " ms"); - } - }; - @Test public void nestedStrongEmphasis() { // this is limited by the stack size because visitor is recursive diff --git a/commonmark/src/test/java/org/commonmark/test/RegressionTest.java b/commonmark/src/test/java/org/commonmark/test/RegressionTest.java index 94b3a7439..900a6518c 100644 --- a/commonmark/src/test/java/org/commonmark/test/RegressionTest.java +++ b/commonmark/src/test/java/org/commonmark/test/RegressionTest.java @@ -6,18 +6,18 @@ import org.commonmark.testutil.TestResources; import org.commonmark.testutil.example.Example; import org.commonmark.testutil.example.ExampleReader; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; -import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class RegressionTest extends RenderingTestCase { private static final Parser PARSER = Parser.builder().build(); @@ -26,20 +26,13 @@ public class RegressionTest extends RenderingTestCase { private static final Map OVERRIDDEN_EXAMPLES = getOverriddenExamples(); - private final Example example; + @Parameter + Example example; - public RegressionTest(Example example) { - this.example = example; - } - - @Parameters(name = "{0}") - public static List data() { - List data = new ArrayList<>(); - for (URL regressionResource : TestResources.getRegressions()) { - List examples = ExampleReader.readExamples(regressionResource); - for (Example example : examples) { - data.add(new Object[]{example}); - } + static List data() { + var data = new ArrayList(); + for (var regressionResource : TestResources.getRegressions()) { + data.addAll(ExampleReader.readExamples(regressionResource)); } return data; } diff --git a/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java b/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java index 3fb95d386..5d34bf410 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java @@ -2,9 +2,10 @@ import org.commonmark.node.SourceSpan; import org.commonmark.parser.SourceLine; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class SourceLineTest { @@ -25,18 +26,20 @@ public void testSubstring() { assertSourceLine(line.substring(4, 4), "", null); } - @Test(expected = StringIndexOutOfBoundsException.class) + @Test public void testSubstringBeginOutOfBounds() { - SourceLine.of("abcd", SourceSpan.of(3, 10, 13, 4)).substring(3, 2); + var sourceLine = SourceLine.of("abcd", SourceSpan.of(3, 10, 13, 4)); + assertThatThrownBy(() -> sourceLine.substring(3, 2)).isInstanceOf(StringIndexOutOfBoundsException.class); } - @Test(expected = StringIndexOutOfBoundsException.class) + @Test public void testSubstringEndOutOfBounds() { - SourceLine.of("abcd", SourceSpan.of(3, 10, 13, 4)).substring(0, 5); + var sourceLine = SourceLine.of("abcd", SourceSpan.of(3, 10, 13, 4)); + assertThatThrownBy(() -> sourceLine.substring(0, 5)).isInstanceOf(StringIndexOutOfBoundsException.class); } private static void assertSourceLine(SourceLine sourceLine, String expectedContent, SourceSpan expectedSourceSpan) { - assertEquals(expectedContent, sourceLine.getContent()); - assertEquals(expectedSourceSpan, sourceLine.getSourceSpan()); + assertThat(sourceLine.getContent()).isEqualTo(expectedContent); + assertThat(sourceLine.getSourceSpan()).isEqualTo(expectedSourceSpan); } } diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpanTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpanTest.java index 57048b90e..f1bb231f4 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpanTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpanTest.java @@ -1,10 +1,10 @@ package org.commonmark.test; import org.commonmark.node.SourceSpan; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class SourceSpanTest { @@ -12,52 +12,57 @@ public class SourceSpanTest { public void testSubSpan() { var span = SourceSpan.of(1, 2, 3, 5); - assertSame(span.subSpan(0), span); - assertSame(span.subSpan(0, 5), span); + assertThat(span.subSpan(0)).isSameAs(span); + assertThat(span.subSpan(0, 5)).isSameAs(span); - assertEquals(SourceSpan.of(1, 3, 4, 4), span.subSpan(1)); - assertEquals(SourceSpan.of(1, 4, 5, 3), span.subSpan(2)); - assertEquals(SourceSpan.of(1, 5, 6, 2), span.subSpan(3)); - assertEquals(SourceSpan.of(1, 6, 7, 1), span.subSpan(4)); + assertThat(span.subSpan(1)).isEqualTo(SourceSpan.of(1, 3, 4, 4)); + assertThat(span.subSpan(2)).isEqualTo(SourceSpan.of(1, 4, 5, 3)); + assertThat(span.subSpan(3)).isEqualTo(SourceSpan.of(1, 5, 6, 2)); + assertThat(span.subSpan(4)).isEqualTo(SourceSpan.of(1, 6, 7, 1)); // Not sure if empty spans are useful, but it probably makes sense to mirror how substrings work - assertEquals(SourceSpan.of(1, 7, 8, 0), span.subSpan(5)); - assertEquals("", "abcde".substring(5)); - - assertEquals(SourceSpan.of(1, 2, 3, 5), span.subSpan(0, 5)); - assertEquals(SourceSpan.of(1, 2, 3, 4), span.subSpan(0, 4)); - assertEquals(SourceSpan.of(1, 2, 3, 3), span.subSpan(0, 3)); - assertEquals(SourceSpan.of(1, 2, 3, 2), span.subSpan(0, 2)); - assertEquals(SourceSpan.of(1, 2, 3, 1), span.subSpan(0, 1)); - assertEquals(SourceSpan.of(1, 2, 3, 0), span.subSpan(0, 0)); - assertEquals("a", "abcde".substring(0, 1)); - assertEquals("", "abcde".substring(0, 0)); - - assertEquals(SourceSpan.of(1, 3, 4, 3), span.subSpan(1, 4)); - assertEquals(SourceSpan.of(1, 4, 5, 1), span.subSpan(2, 3)); + assertThat(span.subSpan(5)).isEqualTo(SourceSpan.of(1, 7, 8, 0)); + assertThat("abcde".substring(5)).isEqualTo(""); + + assertThat(span.subSpan(0, 5)).isEqualTo(SourceSpan.of(1, 2, 3, 5)); + assertThat(span.subSpan(0, 4)).isEqualTo(SourceSpan.of(1, 2, 3, 4)); + assertThat(span.subSpan(0, 3)).isEqualTo(SourceSpan.of(1, 2, 3, 3)); + assertThat(span.subSpan(0, 2)).isEqualTo(SourceSpan.of(1, 2, 3, 2)); + assertThat(span.subSpan(0, 1)).isEqualTo(SourceSpan.of(1, 2, 3, 1)); + assertThat(span.subSpan(0, 0)).isEqualTo(SourceSpan.of(1, 2, 3, 0)); + assertThat("abcde".substring(0, 1)).isEqualTo("a"); + assertThat("abcde".substring(0, 0)).isEqualTo(""); + + assertThat(span.subSpan(1, 4)).isEqualTo(SourceSpan.of(1, 3, 4, 3)); + assertThat(span.subSpan(2, 3)).isEqualTo(SourceSpan.of(1, 4, 5, 1)); } - @Test(expected = IndexOutOfBoundsException.class) + @Test public void testSubSpanBeginIndexNegative() { - SourceSpan.of(1, 2, 3, 5).subSpan(-1); + var sourceSpan = SourceSpan.of(1, 2, 3, 5); + assertThatThrownBy(() -> sourceSpan.subSpan(-1)).isInstanceOf(IndexOutOfBoundsException.class); } - @Test(expected = IndexOutOfBoundsException.class) + @Test public void testSubSpanBeginIndexOutOfBounds() { - SourceSpan.of(1, 2, 3, 5).subSpan(6); + var sourceSpan = SourceSpan.of(1, 2, 3, 5); + assertThatThrownBy(() -> sourceSpan.subSpan(6)).isInstanceOf(IndexOutOfBoundsException.class); } - @Test(expected = IndexOutOfBoundsException.class) + @Test public void testSubSpanEndIndexNegative() { - SourceSpan.of(1, 2, 3, 5).subSpan(0, -1); + var sourceSpan = SourceSpan.of(1, 2, 3, 5); + assertThatThrownBy(() -> sourceSpan.subSpan(0, -1)).isInstanceOf(IndexOutOfBoundsException.class); } - @Test(expected = IndexOutOfBoundsException.class) + @Test public void testSubSpanEndIndexOutOfBounds() { - SourceSpan.of(1, 2, 3, 5).subSpan(0, 6); + var sourceSpan = SourceSpan.of(1, 2, 3, 5); + assertThatThrownBy(() -> sourceSpan.subSpan(0, 6)).isInstanceOf(IndexOutOfBoundsException.class); } - @Test(expected = IndexOutOfBoundsException.class) + @Test public void testSubSpanBeginIndexGreaterThanEndIndex() { - SourceSpan.of(1, 2, 3, 5).subSpan(2, 1); + var sourceSpan = SourceSpan.of(1, 2, 3, 5); + assertThatThrownBy(() -> sourceSpan.subSpan(2, 1)).isInstanceOf(IndexOutOfBoundsException.class); } } diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index 23249d930..f4e9d0a17 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -3,7 +3,7 @@ import org.commonmark.node.*; import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.StringReader; @@ -11,7 +11,7 @@ import java.util.Deque; import java.util.List; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class SourceSpansTest { @@ -90,7 +90,7 @@ public void fencedCodeBlock() { Node document = PARSER.parse("```\nfoo\n```\nbar\n"); Paragraph paragraph = (Paragraph) document.getLastChild(); - assertEquals(List.of(SourceSpan.of(3, 0, 12, 3)), paragraph.getSourceSpans()); + assertThat(paragraph.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 0, 12, 3))); } @Test @@ -133,7 +133,7 @@ public void listBlock() { Node document = PARSER.parse("* foo\n * bar\n"); ListBlock listBlock = (ListBlock) document.getFirstChild().getFirstChild().getLastChild(); - assertEquals(List.of(SourceSpan.of(1, 2, 8, 5)), listBlock.getSourceSpans()); + assertThat(listBlock.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(1, 2, 8, 5))); } @Test @@ -158,10 +158,10 @@ public void linkReferenceDefinition() { Node document = PARSER.parse("[foo]: /url\ntext\n"); LinkReferenceDefinition linkReferenceDefinition = (LinkReferenceDefinition) document.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 11)), linkReferenceDefinition.getSourceSpans()); + assertThat(linkReferenceDefinition.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 11))); Paragraph paragraph = (Paragraph) document.getLastChild(); - assertEquals(List.of(SourceSpan.of(1, 0, 12, 4)), paragraph.getSourceSpans()); + assertThat(paragraph.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(1, 0, 12, 4))); } @Test @@ -169,8 +169,8 @@ public void linkReferenceDefinitionMultiple() { var doc = PARSER.parse("[foo]: /foo\n[bar]: /bar\n"); var def1 = (LinkReferenceDefinition) doc.getFirstChild(); var def2 = (LinkReferenceDefinition) doc.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 11)), def1.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(1, 0, 12, 11)), def2.getSourceSpans()); + assertThat(def1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 11))); + assertThat(def2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(1, 0, 12, 11))); } @Test @@ -178,8 +178,8 @@ public void linkReferenceDefinitionWithTitle() { var doc = PARSER.parse("[1]: #not-code \"Text\"\n[foo]: /foo\n"); var def1 = (LinkReferenceDefinition) doc.getFirstChild(); var def2 = (LinkReferenceDefinition) doc.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 21)), def1.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(1, 0, 22, 11)), def2.getSourceSpans()); + assertThat(def1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 21))); + assertThat(def2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(1, 0, 22, 11))); } @Test @@ -187,8 +187,8 @@ public void linkReferenceDefinitionWithTitleInvalid() { var doc = PARSER.parse("[foo]: /url\n\"title\" ok\n"); var def = Nodes.find(doc, LinkReferenceDefinition.class); var paragraph = Nodes.find(doc, Paragraph.class); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 11)), def.getSourceSpans()); - assertEquals(List.of(SourceSpan.of(1, 0, 12, 10)), paragraph.getSourceSpans()); + assertThat(def.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 11))); + assertThat(paragraph.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(1, 0, 12, 10))); } @Test @@ -198,10 +198,10 @@ public void linkReferenceDefinitionHeading() { Node document = PARSER.parse("[foo]: /url\nHeading\n===\n"); LinkReferenceDefinition linkReferenceDefinition = (LinkReferenceDefinition) document.getFirstChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 11)), linkReferenceDefinition.getSourceSpans()); + assertThat(linkReferenceDefinition.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 11))); Heading heading = (Heading) document.getLastChild(); - assertEquals(List.of(SourceSpan.of(1, 0, 12, 7), SourceSpan.of(2, 0, 20, 3)), heading.getSourceSpans()); + assertThat(heading.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(1, 0, 12, 7), SourceSpan.of(2, 0, 20, 3))); } @Test @@ -212,13 +212,13 @@ public void lazyContinuationLines() { var doc = PARSER.parse("> > > foo\nbar\n"); var bq1 = (BlockQuote) doc.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 9), SourceSpan.of(1, 0, 10, 3)), bq1.getSourceSpans()); + assertThat(bq1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 9), SourceSpan.of(1, 0, 10, 3))); var bq2 = (BlockQuote) bq1.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 2, 2, 7), SourceSpan.of(1, 0, 10, 3)), bq2.getSourceSpans()); + assertThat(bq2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 2, 2, 7), SourceSpan.of(1, 0, 10, 3))); var bq3 = (BlockQuote) bq2.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 4, 4, 5), SourceSpan.of(1, 0, 10, 3)), bq3.getSourceSpans()); + assertThat(bq3.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 4, 4, 5), SourceSpan.of(1, 0, 10, 3))); var paragraph = (Paragraph) bq3.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 6, 6, 3), SourceSpan.of(1, 0, 10, 3)), paragraph.getSourceSpans()); + assertThat(paragraph.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 6, 6, 3), SourceSpan.of(1, 0, 10, 3))); } { @@ -226,13 +226,13 @@ public void lazyContinuationLines() { var doc = PARSER.parse("> > > foo\nbars\n"); var bq1 = (BlockQuote) doc.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 9), SourceSpan.of(1, 0, 10, 4)), bq1.getSourceSpans()); + assertThat(bq1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 9), SourceSpan.of(1, 0, 10, 4))); var bq2 = (BlockQuote) bq1.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 2, 2, 7), SourceSpan.of(1, 0, 10, 4)), bq2.getSourceSpans()); + assertThat(bq2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 2, 2, 7), SourceSpan.of(1, 0, 10, 4))); var bq3 = (BlockQuote) bq2.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 4, 4, 5), SourceSpan.of(1, 0, 10, 4)), bq3.getSourceSpans()); + assertThat(bq3.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 4, 4, 5), SourceSpan.of(1, 0, 10, 4))); var paragraph = (Paragraph) bq3.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 6, 6, 3), SourceSpan.of(1, 0, 10, 4)), paragraph.getSourceSpans()); + assertThat(paragraph.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 6, 6, 3), SourceSpan.of(1, 0, 10, 4))); } { @@ -240,15 +240,15 @@ public void lazyContinuationLines() { var doc = PARSER.parse("> 1. > Blockquote\ncontinued here."); var bq1 = (BlockQuote) doc.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 17), SourceSpan.of(1, 0, 18, 15)), bq1.getSourceSpans()); + assertThat(bq1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 17), SourceSpan.of(1, 0, 18, 15))); var orderedList = (OrderedList) bq1.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 2, 2, 15), SourceSpan.of(1, 0, 18, 15)), orderedList.getSourceSpans()); + assertThat(orderedList.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 2, 2, 15), SourceSpan.of(1, 0, 18, 15))); var listItem = (ListItem) orderedList.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 2, 2, 15), SourceSpan.of(1, 0, 18, 15)), listItem.getSourceSpans()); + assertThat(listItem.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 2, 2, 15), SourceSpan.of(1, 0, 18, 15))); var bq2 = (BlockQuote) listItem.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 5, 5, 12), SourceSpan.of(1, 0, 18, 15)), bq2.getSourceSpans()); + assertThat(bq2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 5, 5, 12), SourceSpan.of(1, 0, 18, 15))); var paragraph = (Paragraph) bq2.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 7, 7, 10), SourceSpan.of(1, 0, 18, 15)), paragraph.getSourceSpans()); + assertThat(paragraph.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 7, 7, 10), SourceSpan.of(1, 0, 18, 15))); } { @@ -256,11 +256,11 @@ public void lazyContinuationLines() { var doc = PARSER.parse("> > foo\n> bar\n"); var bq1 = (BlockQuote) doc.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 5)), bq1.getSourceSpans()); + assertThat(bq1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 5))); var bq2 = (BlockQuote) bq1.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 2, 2, 5), SourceSpan.of(1, 2, 10, 3)), bq2.getSourceSpans()); + assertThat(bq2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 2, 2, 5), SourceSpan.of(1, 2, 10, 3))); var paragraph = (Paragraph) bq2.getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 4, 4, 3), SourceSpan.of(1, 2, 10, 3)), paragraph.getSourceSpans()); + assertThat(paragraph.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 4, 4, 3), SourceSpan.of(1, 2, 10, 3))); } } @@ -354,7 +354,7 @@ public void inlineEmphasis() { Node document = INLINES_PARSER.parse("*hey**"); Node lastText = document.getFirstChild().getLastChild(); - assertEquals(List.of(SourceSpan.of(0, 5, 5, 1)), lastText.getSourceSpans()); + assertThat(lastText.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 5, 5, 1))); } @Test @@ -381,8 +381,8 @@ public void differentLineTerminators() { private void assertVisualize(String source, String expected) { var doc = PARSER.parse(source); - assertEquals(expected, SourceSpanRenderer.renderWithLineColumn(doc, source)); - assertEquals(expected, SourceSpanRenderer.renderWithInputIndex(doc, source)); + assertThat(SourceSpanRenderer.renderWithLineColumn(doc, source)).isEqualTo(expected); + assertThat(SourceSpanRenderer.renderWithInputIndex(doc, source)).isEqualTo(expected); } private static void assertSpans(String input, Class nodeClass, SourceSpan... expectedSourceSpans) { @@ -405,7 +405,7 @@ private static void assertInlineSpans(String input, Class nodeCl private static void assertSpans(Node rootNode, Class nodeClass, SourceSpan... expectedSourceSpans) { Node node = findNode(rootNode, nodeClass); - assertEquals(List.of(expectedSourceSpans), node.getSourceSpans()); + assertThat(node.getSourceSpans()).isEqualTo(List.of(expectedSourceSpans)); } private static Node findNode(Node rootNode, Class nodeClass) { diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java index 8d284c6f9..fefd8fb30 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java @@ -7,10 +7,10 @@ import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.SpecTestCase; import org.commonmark.testutil.example.Example; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.fail; import static org.commonmark.testutil.Asserts.assertRendering; -import static org.junit.Assert.fail; public class SpecCoreTest extends SpecTestCase { @@ -18,10 +18,6 @@ public class SpecCoreTest extends SpecTestCase { // The spec says URL-escaping is optional, but the examples assume that it's enabled. private static final HtmlRenderer RENDERER = HtmlRenderer.builder().percentEncodeUrls(true).build(); - public SpecCoreTest(Example example) { - super(example); - } - @Test public void testTextNodesContiguous() { final String source = example.getSource(); diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java index ab0103e5a..47ca3da4e 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java @@ -4,7 +4,7 @@ import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.SpecTestCase; import org.commonmark.testutil.example.Example; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.commonmark.testutil.Asserts.assertRendering; @@ -17,10 +17,6 @@ public class SpecCrLfCoreTest extends SpecTestCase { // The spec says URL-escaping is optional, but the examples assume that it's enabled. private static final HtmlRenderer RENDERER = HtmlRenderer.builder().percentEncodeUrls(true).build(); - public SpecCrLfCoreTest(Example example) { - super(example); - } - @Test public void testHtmlRendering() { assertRendering(example.getSource(), example.getHtml(), render(example.getSource())); diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 770a1aa21..2ebac1711 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -1,6 +1,6 @@ package org.commonmark.test; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class SpecialInputTest extends CoreRenderingTestCase { diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index 93ca87d93..bc443e0e2 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -9,7 +9,7 @@ import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.parser.Parser; import org.commonmark.testutil.Asserts; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Set; diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java index 0be668a70..a9f37792e 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java @@ -1,9 +1,9 @@ package org.commonmark.test; import org.commonmark.renderer.text.TextContentWriter; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.assertThat; public class TextContentWriterTest { @@ -14,7 +14,7 @@ public void whitespace() throws Exception { writer.write("foo"); writer.whitespace(); writer.write("bar"); - assertEquals("foo bar", stringBuilder.toString()); + assertThat(stringBuilder.toString()).isEqualTo("foo bar"); } @Test @@ -24,7 +24,7 @@ public void colon() throws Exception { writer.write("foo"); writer.colon(); writer.write("bar"); - assertEquals("foo:bar", stringBuilder.toString()); + assertThat(stringBuilder.toString()).isEqualTo("foo:bar"); } @Test @@ -34,7 +34,7 @@ public void line() throws Exception { writer.write("foo"); writer.line(); writer.write("bar"); - assertEquals("foo\nbar", stringBuilder.toString()); + assertThat(stringBuilder.toString()).isEqualTo("foo\nbar"); } @Test @@ -42,7 +42,7 @@ public void writeStripped() throws Exception { StringBuilder stringBuilder = new StringBuilder(); TextContentWriter writer = new TextContentWriter(stringBuilder); writer.writeStripped("foo\n bar"); - assertEquals("foo bar", stringBuilder.toString()); + assertThat(stringBuilder.toString()).isEqualTo("foo bar"); } @Test @@ -50,6 +50,6 @@ public void write() throws Exception { StringBuilder stringBuilder = new StringBuilder(); TextContentWriter writer = new TextContentWriter(stringBuilder); writer.writeStripped("foo bar"); - assertEquals("foo bar", stringBuilder.toString()); + assertThat(stringBuilder.toString()).isEqualTo("foo bar"); } } diff --git a/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java b/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java index 2b15f8add..1d564cca2 100644 --- a/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ThematicBreakParserTest.java @@ -2,9 +2,9 @@ import org.commonmark.node.ThematicBreak; import org.commonmark.parser.Parser; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class ThematicBreakParserTest { @@ -20,6 +20,6 @@ public void testLiteral() { private static void assertLiteral(String expected, String input) { var tb = Nodes.find(PARSER.parse(input), ThematicBreak.class); - assertEquals(expected, tb.getLiteral()); + assertThat(tb.getLiteral()).isEqualTo(expected); } } diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 63901a49b..20cd9f5ab 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -6,8 +6,8 @@ import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.*; import org.commonmark.renderer.markdown.MarkdownRenderer; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.io.FileInputStream; import java.io.IOException; @@ -16,7 +16,7 @@ import java.util.Map; import java.util.Set; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class UsageExampleTest { @@ -25,7 +25,7 @@ public void parseAndRender() { Parser parser = Parser.builder().build(); Node document = parser.parse("This is *Markdown*"); HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build(); - assertEquals("

    This is Markdown

    \n", renderer.render(document)); + assertThat(renderer.render(document)).isEqualTo("

    This is Markdown

    \n"); } @Test @@ -37,11 +37,11 @@ public void renderToMarkdown() { heading.appendChild(new Text("My title")); document.appendChild(heading); - assertEquals("## My title\n", renderer.render(document)); + assertThat(renderer.render(document)).isEqualTo("## My title\n"); } @Test - @Ignore + @Disabled public void parseReaderRender() throws IOException { Parser parser = Parser.builder().build(); try (InputStreamReader reader = new InputStreamReader(new FileInputStream("file.md"), StandardCharsets.UTF_8)) { @@ -56,7 +56,7 @@ public void visitor() { Node node = parser.parse("Example\n=======\n\nSome more text"); WordCountVisitor visitor = new WordCountVisitor(); node.accept(visitor); - assertEquals(4, visitor.wordCount); + assertThat(visitor.wordCount).isEqualTo(4); } @Test @@ -67,11 +67,11 @@ public void sourcePositions() { var doc = parser.parse(source); var emphasis = doc.getLastChild().getLastChild(); var s = emphasis.getSourceSpans().get(0); - assertEquals(2, s.getLineIndex()); - assertEquals(4, s.getColumnIndex()); - assertEquals(9, s.getInputIndex()); - assertEquals(5, s.getLength()); - assertEquals("*baz*", source.substring(s.getInputIndex(), s.getInputIndex() + s.getLength())); + assertThat(s.getLineIndex()).isEqualTo(2); + assertThat(s.getColumnIndex()).isEqualTo(4); + assertThat(s.getInputIndex()).isEqualTo(9); + assertThat(s.getLength()).isEqualTo(5); + assertThat(source.substring(s.getInputIndex(), s.getInputIndex() + s.getLength())).isEqualTo("*baz*"); } @Test @@ -87,8 +87,7 @@ public AttributeProvider create(AttributeProviderContext context) { .build(); Node document = parser.parse("![text](/url.png)"); - assertEquals("

    \"text\"

    \n", - renderer.render(document)); + assertThat(renderer.render(document)).isEqualTo("

    \"text\"

    \n"); } @Test @@ -104,7 +103,7 @@ public NodeRenderer create(HtmlNodeRendererContext context) { .build(); Node document = parser.parse("Example:\n\n code"); - assertEquals("

    Example:

    \n
    code\n
    \n", renderer.render(document)); + assertThat(renderer.render(document)).isEqualTo("

    Example:

    \n
    code\n
    \n"); } class WordCountVisitor extends AbstractVisitor { diff --git a/commonmark/src/test/java/org/commonmark/text/CharactersTest.java b/commonmark/src/test/java/org/commonmark/text/CharactersTest.java index a362cf53c..99f510cb7 100644 --- a/commonmark/src/test/java/org/commonmark/text/CharactersTest.java +++ b/commonmark/src/test/java/org/commonmark/text/CharactersTest.java @@ -1,9 +1,8 @@ package org.commonmark.text; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class CharactersTest { @@ -18,17 +17,17 @@ public void isPunctuation() { }; for (char c : chars) { - assertTrue("Expected to be punctuation: " + c, Characters.isPunctuationCodePoint(c)); + assertThat(Characters.isPunctuationCodePoint(c)).as("Expected to be punctuation: " + c).isTrue(); } } @Test public void isBlank() { - assertTrue(Characters.isBlank("")); - assertTrue(Characters.isBlank(" ")); - assertTrue(Characters.isBlank("\t")); - assertTrue(Characters.isBlank(" \t")); - assertFalse(Characters.isBlank("a")); - assertFalse(Characters.isBlank("\f")); + assertThat(Characters.isBlank("")).isTrue(); + assertThat(Characters.isBlank(" ")).isTrue(); + assertThat(Characters.isBlank("\t")).isTrue(); + assertThat(Characters.isBlank(" \t")).isTrue(); + assertThat(Characters.isBlank("a")).isFalse(); + assertThat(Characters.isBlank("\f")).isFalse(); } } diff --git a/pom.xml b/pom.xml index 29ca4ead2..db4003e51 100644 --- a/pom.xml +++ b/pom.xml @@ -79,7 +79,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.5 + 3.5.3 @@ -182,9 +182,14 @@ - junit - junit - 4.13.1 + org.junit.jupiter + junit-jupiter + 5.13.1 + + + org.assertj + assertj-core + 3.27.3 org.openjdk.jmh From add32d6e3aa3035dc3e4f59babfaa6760f275989 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 15 Jun 2025 21:25:51 +1000 Subject: [PATCH 200/345] Bump maven plugins --- pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index db4003e51..d2cc86712 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + 3.14.0 11 @@ -47,7 +47,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.4.1 + 3.4.2 ${project.build.outputDirectory}/META-INF/MANIFEST.MF @@ -57,12 +57,12 @@ org.apache.maven.plugins maven-install-plugin - 3.1.1 + 3.1.4 org.apache.maven.plugins maven-javadoc-plugin - 3.6.3 + 3.11.2 *.internal,*.internal.* @@ -237,7 +237,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.4 + 3.2.7 sign-artifacts @@ -264,7 +264,7 @@ org.jacoco jacoco-maven-plugin - 0.8.12 + 0.8.13 From bb57a5f0921ab7e46f44128c875720617452564a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 20 Jun 2025 21:55:30 +1000 Subject: [PATCH 201/345] Parse tables without blank line before GitHub doesn't mention it in their spec but it does work, e.g.: text |a| |---| |b| Parses as a paragraph with "text", and a table. --- CHANGELOG.md | 2 + .../gfm/tables/internal/TableBlockParser.java | 6 +- .../commonmark/ext/gfm/tables/TablesTest.java | 58 +++++++- .../commonmark/internal/BlockStartImpl.java | 13 ++ .../commonmark/internal/DocumentParser.java | 41 +++--- .../commonmark/internal/HeadingParser.java | 2 +- .../LinkReferenceDefinitionParser.java | 19 +++ .../commonmark/internal/ParagraphParser.java | 4 + .../commonmark/parser/block/BlockStart.java | 41 ++++++ .../parser/block/MatchedBlockParser.java | 3 +- .../test/BlockParserFactoryTest.java | 127 ++++++++++++++++++ .../java/org/commonmark/test/ParserTest.java | 41 ------ 12 files changed, 286 insertions(+), 71 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/test/BlockParserFactoryTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0808edb72..7ae5ffed2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ with the exception that 0.x versions can break between minor versions. ### Added - More documentation with examples for `Node` classes ### Changed +- GitHub tables: Tables are now parsed even if there's no blank line before the + table heading, matching GitHub's behavior. ### Fixed - `MarkdownRenderer`: Fix precedence for `nodeRendererFactory`: Factories passed to the builder can now override rendering for core node types. diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index 0dfdd3159..57af128d8 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java @@ -274,17 +274,17 @@ public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { List paragraphLines = matchedBlockParser.getParagraphLines().getLines(); - if (paragraphLines.size() == 1 && Characters.find('|', paragraphLines.get(0).getContent(), 0) != -1) { + if (paragraphLines.size() >= 1 && Characters.find('|', paragraphLines.get(paragraphLines.size() - 1).getContent(), 0) != -1) { SourceLine line = state.getLine(); SourceLine separatorLine = line.substring(state.getIndex(), line.getContent().length()); List columns = parseSeparator(separatorLine.getContent()); if (columns != null && !columns.isEmpty()) { - SourceLine paragraph = paragraphLines.get(0); + SourceLine paragraph = paragraphLines.get(paragraphLines.size() - 1); List headerCells = split(paragraph); if (columns.size() >= headerCells.size()) { return BlockStart.of(new TableBlockParser(columns, paragraph)) .atIndex(state.getIndex()) - .replaceActiveBlockParser(); + .replaceParagraphLines(1); } } } diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java index 3135b4d8e..3f4b37d54 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -1,8 +1,7 @@ package org.commonmark.ext.gfm.tables; import org.commonmark.Extension; -import org.commonmark.node.Node; -import org.commonmark.node.SourceSpan; +import org.commonmark.node.*; import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.AttributeProvider; @@ -79,11 +78,6 @@ public void separatorNeedsPipes() { assertRendering("Abc|Def\n|--- ---", "

    Abc|Def\n|--- ---

    \n"); } - @Test - public void headerMustBeOneLine() { - assertRendering("No\nAbc|Def\n---|---", "

    No\nAbc|Def\n---|---

    \n"); - } - @Test public void oneHeadNoBody() { assertRendering("Abc|Def\n---|---", "\n" + @@ -702,6 +696,26 @@ public void danglingPipe() { "

    |

    \n"); } + @Test + public void interruptsParagraph() { + assertRendering("text\n" + + "|a |\n" + + "|---|\n" + + "|b |", "

    text

    \n" + + "
    \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    a
    b
    \n"); + } + @Test public void attributeProviderIsApplied() { AttributeProviderFactory factory = new AttributeProviderFactory() { @@ -835,6 +849,36 @@ public void sourceSpans() { assertThat(bodyRow3Cell2.getSourceSpans()).isEqualTo(List.of()); } + @Test + public void sourceSpansWhenInterrupting() { + var parser = Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); + var document = parser.parse("a\n" + + "bc\n" + + "|de|\n" + + "|---|\n" + + "|fg|"); + + var paragraph = (Paragraph) document.getFirstChild(); + var text = (Text) paragraph.getFirstChild(); + assertThat(text.getLiteral()).isEqualTo("a"); + assertThat(text.getNext()).isInstanceOf(SoftLineBreak.class); + var text2 = (Text) text.getNext().getNext(); + assertThat(text2.getLiteral()).isEqualTo("bc"); + + assertThat(paragraph.getSourceSpans()).isEqualTo(List.of( + SourceSpan.of(0, 0, 0, 1), + SourceSpan.of(1, 0, 2, 2))); + + var table = (TableBlock) document.getLastChild(); + assertThat(table.getSourceSpans()).isEqualTo(List.of( + SourceSpan.of(2, 0, 5, 4), + SourceSpan.of(3, 0, 10, 5), + SourceSpan.of(4, 0, 16, 4))); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); diff --git a/commonmark/src/main/java/org/commonmark/internal/BlockStartImpl.java b/commonmark/src/main/java/org/commonmark/internal/BlockStartImpl.java index c7e967d46..516f944b2 100644 --- a/commonmark/src/main/java/org/commonmark/internal/BlockStartImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/BlockStartImpl.java @@ -9,6 +9,7 @@ public class BlockStartImpl extends BlockStart { private int newIndex = -1; private int newColumn = -1; private boolean replaceActiveBlockParser = false; + private int replaceParagraphLines = 0; public BlockStartImpl(BlockParser... blockParsers) { this.blockParsers = blockParsers; @@ -30,6 +31,10 @@ public boolean isReplaceActiveBlockParser() { return replaceActiveBlockParser; } + int getReplaceParagraphLines() { + return replaceParagraphLines; + } + @Override public BlockStart atIndex(int newIndex) { this.newIndex = newIndex; @@ -48,4 +53,12 @@ public BlockStart replaceActiveBlockParser() { return this; } + @Override + public BlockStart replaceParagraphLines(int lines) { + if (!(lines >= 1)) { + throw new IllegalArgumentException("Lines must be >= 1"); + } + this.replaceParagraphLines = lines; + return this; + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 6059cc51c..d935f8d27 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -270,9 +270,15 @@ private void parseLine(String ln, int inputIndex) { } List replacedSourceSpans = null; - if (blockStart.isReplaceActiveBlockParser()) { - Block replacedBlock = prepareActiveBlockParserForReplacement(); - replacedSourceSpans = replacedBlock.getSourceSpans(); + if (blockStart.getReplaceParagraphLines() >= 1 || blockStart.isReplaceActiveBlockParser()) { + var activeBlockParser = getActiveBlockParser(); + if (activeBlockParser instanceof ParagraphParser) { + var paragraphParser = (ParagraphParser) activeBlockParser; + var lines = blockStart.isReplaceActiveBlockParser() ? Integer.MAX_VALUE : blockStart.getReplaceParagraphLines(); + replacedSourceSpans = replaceParagraphLines(lines, paragraphParser); + } else if (blockStart.isReplaceActiveBlockParser()) { + replacedSourceSpans = prepareActiveBlockParserForReplacement(activeBlockParser); + } } for (BlockParser newBlockParser : blockStart.getBlockParsers()) { @@ -498,24 +504,23 @@ private OpenBlockParser deactivateBlockParser() { return openBlockParsers.remove(openBlockParsers.size() - 1); } - private Block prepareActiveBlockParserForReplacement() { - // Note that we don't want to parse inlines, as it's getting replaced. - BlockParser old = deactivateBlockParser().blockParser; + private List replaceParagraphLines(int lines, ParagraphParser paragraphParser) { + // Remove lines from paragraph as the new block is using them. + // If all lines are used, this also unlinks the Paragraph block. + var sourceSpans = paragraphParser.removeLines(lines); + // Close the paragraph block parser, which will finalize it. + closeBlockParsers(1); + return sourceSpans; + } - if (old instanceof ParagraphParser) { - ParagraphParser paragraphParser = (ParagraphParser) old; - // Collect any link reference definitions. Note that replacing the active block parser is done after a - // block parser got the current paragraph content using MatchedBlockParser#getContentString. In case the - // paragraph started with link reference definitions, we parse and strip them before the block parser gets - // the content. We want to keep them. - // If no replacement happens, we collect the definitions as part of finalizing blocks. - addDefinitionsFrom(paragraphParser); - } + private List prepareActiveBlockParserForReplacement(BlockParser blockParser) { + // Note that we don't want to parse inlines here, as it's getting replaced. + deactivateBlockParser(); // Do this so that source positions are calculated, which we will carry over to the replacing block. - old.closeBlock(); - old.getBlock().unlink(); - return old.getBlock(); + blockParser.closeBlock(); + blockParser.getBlock().unlink(); + return blockParser.getBlock().getSourceSpans(); } private Document finalizeAndProcess() { diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index 3bc9ba5c4..05f070137 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -60,7 +60,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar if (!paragraph.isEmpty()) { return BlockStart.of(new HeadingParser(setextHeadingLevel, paragraph)) .atIndex(line.getContent().length()) - .replaceActiveBlockParser(); + .replaceParagraphLines(paragraph.getLines().size()); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index b58e669ef..637d3b111 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -10,6 +10,7 @@ import org.commonmark.parser.beta.Scanner; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -102,6 +103,14 @@ State getState() { return state; } + List removeLines(int lines) { + var removedSpans = Collections.unmodifiableList(new ArrayList<>( + sourceSpans.subList(Math.max(sourceSpans.size() - lines, 0), sourceSpans.size()))); + removeLast(lines, paragraphLines); + removeLast(lines, sourceSpans); + return removedSpans; + } + private boolean startDefinition(Scanner scanner) { // Finish any outstanding references now. We don't do this earlier because we need addSourceSpan to have been // called before we do it. @@ -269,6 +278,16 @@ private void finishReference() { title = null; } + private static void removeLast(int n, List list) { + if (n >= list.size()) { + list.clear(); + } else { + for (int i = 0; i < n; i++) { + list.remove(list.size() - 1); + } + } + } + enum State { // Looking for the start of a definition, i.e. `[` START_DEFINITION, diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index 93a2dd593..27eb1e647 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -79,4 +79,8 @@ public void parseInlines(InlineParser inlineParser) { public SourceLines getParagraphLines() { return linkReferenceDefinitionParser.getParagraphLines(); } + + public List removeLines(int lines) { + return linkReferenceDefinitionParser.removeLines(lines); + } } diff --git a/commonmark/src/main/java/org/commonmark/parser/block/BlockStart.java b/commonmark/src/main/java/org/commonmark/parser/block/BlockStart.java index d9e7a2b49..c41f1caa3 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockStart.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockStart.java @@ -10,18 +10,59 @@ public abstract class BlockStart { protected BlockStart() { } + /** + * Result for when there is no block start. + */ public static BlockStart none() { return null; } + /** + * Start block(s) with the specified parser(s). + */ public static BlockStart of(BlockParser... blockParsers) { return new BlockStartImpl(blockParsers); } + /** + * Continue parsing at the specified index. + * + * @param newIndex the new index, see {@link ParserState#getIndex()} + */ public abstract BlockStart atIndex(int newIndex); + /** + * Continue parsing at the specified column (for tab handling). + * + * @param newColumn the new column, see {@link ParserState#getColumn()} + */ public abstract BlockStart atColumn(int newColumn); + /** + * @deprecated use {@link #replaceParagraphLines(int)} instead; please raise an issue if that doesn't work for you + * for some reason. + */ + @Deprecated public abstract BlockStart replaceActiveBlockParser(); + /** + * Replace a number of lines from the current paragraph (as returned by + * {@link MatchedBlockParser#getParagraphLines()}) with the new block. + *

    + * This is useful for parsing blocks that start with normal paragraphs and only have special marker syntax in later + * lines, e.g. in this: + *

    +     * Foo
    +     * ===
    +     * 
    + * The Foo line is initially parsed as a normal paragraph, then === is parsed as a heading + * marker, replacing the 1 paragraph line before. The end result is a single Heading block. + *

    + * Note that source spans from the replaced lines are automatically added to the new block. + * + * @param lines the number of lines to replace (at least 1); use {@link Integer#MAX_VALUE} to replace the whole + * paragraph + */ + public abstract BlockStart replaceParagraphLines(int lines); + } diff --git a/commonmark/src/main/java/org/commonmark/parser/block/MatchedBlockParser.java b/commonmark/src/main/java/org/commonmark/parser/block/MatchedBlockParser.java index 1f2bcfb2a..c4619d8c2 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/MatchedBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/MatchedBlockParser.java @@ -12,7 +12,8 @@ public interface MatchedBlockParser { BlockParser getMatchedBlockParser(); /** - * Returns the current paragraph lines if the matched block is a paragraph. + * Returns the current paragraph lines if the matched block is a paragraph. If you want to use some or all of the + * lines for starting a new block instead, use {@link BlockStart#replaceParagraphLines(int)}. * * @return paragraph content or an empty list */ diff --git a/commonmark/src/test/java/org/commonmark/test/BlockParserFactoryTest.java b/commonmark/src/test/java/org/commonmark/test/BlockParserFactoryTest.java new file mode 100644 index 000000000..b733d7970 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/BlockParserFactoryTest.java @@ -0,0 +1,127 @@ +package org.commonmark.test; + +import org.commonmark.node.*; +import org.commonmark.parser.IncludeSourceSpans; +import org.commonmark.parser.InlineParser; +import org.commonmark.parser.Parser; +import org.commonmark.parser.SourceLines; +import org.commonmark.parser.block.*; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BlockParserFactoryTest { + + @Test + public void customBlockParserFactory() { + var parser = Parser.builder().customBlockParserFactory(new DashBlockParser.Factory()).build(); + + // The dashes would normally be a ThematicBreak + var doc = parser.parse("hey\n\n---\n"); + + assertThat(doc.getFirstChild()).isInstanceOf(Paragraph.class); + assertThat(((Text) doc.getFirstChild().getFirstChild()).getLiteral()).isEqualTo("hey"); + assertThat(doc.getLastChild()).isInstanceOf(DashBlock.class); + } + + @Test + public void replaceActiveBlockParser() { + var parser = Parser.builder() + .customBlockParserFactory(new StarHeadingBlockParser.Factory()) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); + + var doc = parser.parse("a\nbc\n***\n"); + + var heading = doc.getFirstChild(); + assertThat(heading).isInstanceOf(StarHeading.class); + assertThat(heading.getNext()).isNull(); + var a = heading.getFirstChild(); + assertThat(a).isInstanceOf(Text.class); + assertThat(((Text) a).getLiteral()).isEqualTo("a"); + var bc = a.getNext().getNext(); + assertThat(bc).isInstanceOf(Text.class); + assertThat(((Text) bc).getLiteral()).isEqualTo("bc"); + assertThat(bc.getNext()).isNull(); + + assertThat(heading.getSourceSpans()).isEqualTo(List.of( + SourceSpan.of(0, 0, 0, 1), + SourceSpan.of(1, 0, 2, 2), + SourceSpan.of(2, 0, 5, 3))); + assertThat(a.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 1))); + assertThat(bc.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(1, 0, 2, 2))); + } + + private static class DashBlock extends CustomBlock { + } + + private static class DashBlockParser extends AbstractBlockParser { + + private DashBlock dash = new DashBlock(); + + @Override + public Block getBlock() { + return dash; + } + + @Override + public BlockContinue tryContinue(ParserState parserState) { + return BlockContinue.none(); + } + + static class Factory extends AbstractBlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + if (state.getLine().getContent().equals("---")) { + return BlockStart.of(new DashBlockParser()); + } + return BlockStart.none(); + } + } + } + + private static class StarHeading extends CustomBlock { + } + + private static class StarHeadingBlockParser extends AbstractBlockParser { + + private final SourceLines content; + private final StarHeading heading = new StarHeading(); + + StarHeadingBlockParser(SourceLines content) { + this.content = content; + } + + @Override + public Block getBlock() { + return heading; + } + + @Override + public BlockContinue tryContinue(ParserState parserState) { + return BlockContinue.none(); + } + + @Override + public void parseInlines(InlineParser inlineParser) { + inlineParser.parse(content, heading); + } + + static class Factory extends AbstractBlockParserFactory { + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + var lines = matchedBlockParser.getParagraphLines(); + if (state.getLine().getContent().toString().startsWith("***")) { + return BlockStart.of(new StarHeadingBlockParser(lines)) + .replaceActiveBlockParser(); + } else { + return BlockStart.none(); + } + } + } + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 3b9ef09e9..c119b5e2d 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -42,18 +42,6 @@ public void ioReaderTest() throws IOException { assertThat(renderer.render(document1)).isEqualTo(renderer.render(document2)); } - @Test - public void customBlockParserFactory() { - Parser parser = Parser.builder().customBlockParserFactory(new DashBlockParserFactory()).build(); - - // The dashes would normally be a ThematicBreak - Node document = parser.parse("hey\n\n---\n"); - - assertThat(document.getFirstChild()).isInstanceOf(Paragraph.class); - assertThat(((Text) document.getFirstChild().getFirstChild()).getLiteral()).isEqualTo("hey"); - assertThat(document.getLastChild()).isInstanceOf(DashBlock.class); - } - @Test public void enabledBlockTypes() { String given = "# heading 1\n\nnot a heading"; @@ -154,33 +142,4 @@ private String firstText(Node n) { } return ((Text) n).getLiteral(); } - - private static class DashBlock extends CustomBlock { - } - - private static class DashBlockParser extends AbstractBlockParser { - - private DashBlock dash = new DashBlock(); - - @Override - public Block getBlock() { - return dash; - } - - @Override - public BlockContinue tryContinue(ParserState parserState) { - return BlockContinue.none(); - } - } - - private static class DashBlockParserFactory extends AbstractBlockParserFactory { - - @Override - public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { - if (state.getLine().getContent().equals("---")) { - return BlockStart.of(new DashBlockParser()); - } - return BlockStart.none(); - } - } } From 971aaa9edeaa7ef161e4553b001be91bf2d83cea Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 20 Jun 2025 22:32:23 +1000 Subject: [PATCH 202/345] MarkdownRenderer: Fix ordered list with long start number (fixes #382) --- CHANGELOG.md | 2 ++ .../renderer/markdown/CoreMarkdownNodeRenderer.java | 2 +- .../commonmark/renderer/markdown/MarkdownRendererTest.java | 7 +++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ae5ffed2..88a333502 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ with the exception that 0.x versions can break between minor versions. ### Fixed - `MarkdownRenderer`: Fix precedence for `nodeRendererFactory`: Factories passed to the builder can now override rendering for core node types. +- `MarkdownRenderer`: Fix exception with ordered lists with a long first number + followed by a shorter one (#382) - Fix warning in Eclipse about "missing 'requires transitive'" - Fix Android incompatibility with `requireNonNullElseGet` diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 32510feaf..5a81676f4 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -257,7 +257,7 @@ public void visit(ListItem listItem) { throw new IllegalStateException("Unknown list holder type: " + listHolder); } Integer contentIndent = listItem.getContentIndent(); - String spaces = contentIndent != null ? repeat(" ", contentIndent - marker.length()) : " "; + String spaces = contentIndent != null ? repeat(" ", Math.max(contentIndent - marker.length(), 1)) : " "; writer.writePrefix(marker); writer.writePrefix(spaces); writer.pushPrefix(repeat(" ", marker.length() + spaces.length())); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 36b8adad9..6a468a08e 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -176,6 +176,13 @@ public void testOrderedListItemsFromAst() { assertRendering("", "2) Test\n", render(doc)); } + @Test + public void testOrderedListItemsWithStartNumberLongerThanLaterNumber() { + var source = "10001.\n20.\n"; + var doc = parse(source); + assertRendering(source, "10001. \n10002. \n", render(doc)); + } + // Inlines @Test From 5e6b740271587bb0881a5f065d56f05f8ae7ed9f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 20 Jun 2025 22:45:24 +1000 Subject: [PATCH 203/345] Migrate from OSSRH to Central Publisher Portal --- .github/workflows/release.yml | 13 +++++++------ pom.xml | 27 ++++++++------------------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4727b103d..c0531ca55 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,11 +19,12 @@ jobs: - name: Set up Maven Central repository uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 24 distribution: 'zulu' - server-id: ossrh - server-username: MAVEN_USERNAME # env variable to use for username in release - server-password: MAVEN_PASSWORD # env variable to use for password in release + # See https://central.sonatype.org/publish/publish-portal-maven/ + server-id: central + server-username: CENTRAL_USERNAME # env variable to use for username in release + server-password: CENTRAL_PASSWORD # env variable to use for password in release gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable to use for passphrase in release @@ -37,6 +38,6 @@ jobs: mvn -B -Dusername=${{ secrets.GH_USERNAME }} -Dpassword=${{ secrets.GH_ACCESS_TOKEN }} release:prepare mvn -B release:perform env: - MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }} MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} diff --git a/pom.xml b/pom.xml index d2cc86712..533d2dc0b 100644 --- a/pom.xml +++ b/pom.xml @@ -85,22 +85,22 @@ + - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 true - ossrh - https://oss.sonatype.org/ - true - 10 + central + true + published org.apache.maven.plugins maven-release-plugin - 3.0.1 + 3.1.1 true false @@ -307,15 +307,4 @@ HEAD - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - From adcf2f232c9fcf55e29770c78a9285d5446517f3 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 20 Jun 2025 22:51:36 +1000 Subject: [PATCH 204/345] Prepare CHANGELOG for release 0.25 --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88a333502..3d103521e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. -## [Unreleased] +## [0.25.0] - 2025-06-20 ### Added +- Include OSGi metadata in jars (`META-INF/MANIFEST.MF` files) - More documentation with examples for `Node` classes ### Changed - GitHub tables: Tables are now parsed even if there's no blank line before the @@ -473,7 +474,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. -[Unreleased]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.24.0...main +[0.25.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.24.0...commonmark-parent-0.25.0 [0.24.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.23.0...commonmark-parent-0.24.0 [0.23.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.22.0...commonmark-parent-0.23.0 [0.22.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.21.0...commonmark-parent-0.22.0 From 6354f0244746a5e6cd6b44f0be61e153681f2270 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 20 Jun 2025 22:52:07 +1000 Subject: [PATCH 205/345] Prepare for version 0.25.0 Ran `mvn versions:set -DnewVersion=0.25.0-SNAPSHOT` --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 22 +++++++++++----------- 13 files changed, 23 insertions(+), 23 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 7f6a5cbc3..03dba9cb4 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index d34e4e2f9..5463aa0f1 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 1fd1269bf..a3d7e3ab5 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index d261a1d24..64eee7d5c 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index e4a62b2b3..7ebe055a5 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 55b0dce20..593d32f37 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 9dbe2cf06..a94cdc229 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 3926c8b5e..6856a9064 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 9e329e0d3..e492ede62 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index c5e572291..072883792 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index d6a38f4a9..55c415dc0 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 17f3bc747..98e33b403 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index d2cc86712..e7a18f915 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,52 +132,52 @@ org.commonmark commonmark - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT org.commonmark commonmark-ext-ins - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT org.commonmark commonmark-test-util - 0.24.1-SNAPSHOT + 0.25.0-SNAPSHOT From 99e6050ae12e862e39f4a0b81108ec155d4817af Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Fri, 20 Jun 2025 12:57:31 +0000 Subject: [PATCH 206/345] [maven-release-plugin] prepare release commonmark-parent-0.25.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 24 ++++++++++++------------ 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 03dba9cb4..aab6e8beb 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0-SNAPSHOT + 0.25.0 commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 5463aa0f1..12ddbf41f 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0-SNAPSHOT + 0.25.0 commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index a3d7e3ab5..e6b203427 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0-SNAPSHOT + 0.25.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 64eee7d5c..fc88b6351 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0-SNAPSHOT + 0.25.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 7ebe055a5..9674f0baa 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0-SNAPSHOT + 0.25.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 593d32f37..7475feef3 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0-SNAPSHOT + 0.25.0 commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index a94cdc229..9a885e6b8 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0-SNAPSHOT + 0.25.0 commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 6856a9064..38b3569de 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0-SNAPSHOT + 0.25.0 commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index e492ede62..7cc681e1d 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.25.0-SNAPSHOT + 0.25.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 072883792..67f4e6da0 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0-SNAPSHOT + 0.25.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 55c415dc0..136aa3d90 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0-SNAPSHOT + 0.25.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 98e33b403..b369b1bda 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0-SNAPSHOT + 0.25.0 commonmark diff --git a/pom.xml b/pom.xml index 5ef080181..60fbf80e0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.25.0-SNAPSHOT + 0.25.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,52 +132,52 @@ org.commonmark commonmark - 0.25.0-SNAPSHOT + 0.25.0 org.commonmark commonmark-ext-autolink - 0.25.0-SNAPSHOT + 0.25.0 org.commonmark commonmark-ext-image-attributes - 0.25.0-SNAPSHOT + 0.25.0 org.commonmark commonmark-ext-ins - 0.25.0-SNAPSHOT + 0.25.0 org.commonmark commonmark-ext-gfm-strikethrough - 0.25.0-SNAPSHOT + 0.25.0 org.commonmark commonmark-ext-gfm-tables - 0.25.0-SNAPSHOT + 0.25.0 org.commonmark commonmark-ext-heading-anchor - 0.25.0-SNAPSHOT + 0.25.0 org.commonmark commonmark-ext-task-list-items - 0.25.0-SNAPSHOT + 0.25.0 org.commonmark commonmark-ext-yaml-front-matter - 0.25.0-SNAPSHOT + 0.25.0 org.commonmark commonmark-test-util - 0.25.0-SNAPSHOT + 0.25.0 @@ -304,7 +304,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - HEAD + commonmark-parent-0.25.0 From 8d5918d4efd3ad1dca4626af8a516872f7aa6121 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Fri, 20 Jun 2025 12:57:32 +0000 Subject: [PATCH 207/345] [maven-release-plugin] prepare for next development iteration --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 24 ++++++++++++------------ 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index aab6e8beb..d0d86d572 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0 + 0.25.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 12ddbf41f..d965c409e 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0 + 0.25.1-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index e6b203427..05f290120 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0 + 0.25.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index fc88b6351..6445eb34a 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0 + 0.25.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 9674f0baa..a1b137d73 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0 + 0.25.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 7475feef3..3f36b72bf 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0 + 0.25.1-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 9a885e6b8..0c04f6311 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0 + 0.25.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 38b3569de..051fc45bb 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0 + 0.25.1-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 7cc681e1d..114d860c0 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.25.0 + 0.25.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 67f4e6da0..d5e108b0a 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0 + 0.25.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 136aa3d90..edad4a84d 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0 + 0.25.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index b369b1bda..22e9795db 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.0 + 0.25.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 60fbf80e0..6fd0c8df9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.25.0 + 0.25.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,52 +132,52 @@ org.commonmark commonmark - 0.25.0 + 0.25.1-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.25.0 + 0.25.1-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.25.0 + 0.25.1-SNAPSHOT org.commonmark commonmark-ext-ins - 0.25.0 + 0.25.1-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.25.0 + 0.25.1-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.25.0 + 0.25.1-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.25.0 + 0.25.1-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.25.0 + 0.25.1-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.25.0 + 0.25.1-SNAPSHOT org.commonmark commonmark-test-util - 0.25.0 + 0.25.1-SNAPSHOT @@ -304,7 +304,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - commonmark-parent-0.25.0 + HEAD From c577edfe08e523ab806a111f629a518c4de660be Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 20 Jun 2025 23:12:27 +1000 Subject: [PATCH 208/345] README: Bump version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 73e69d9df..fa632a96d 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.24.0 + 0.25.0 ``` @@ -291,7 +291,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.24.0 + 0.25.0 ``` From 33737b7d5dd65153f2d022f7b5770f0fa0630296 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 20 Jun 2025 23:15:52 +1000 Subject: [PATCH 209/345] CHANGELOG: Add issue links --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d103521e..346c0d6b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,18 +8,18 @@ with the exception that 0.x versions can break between minor versions. ## [0.25.0] - 2025-06-20 ### Added -- Include OSGi metadata in jars (`META-INF/MANIFEST.MF` files) -- More documentation with examples for `Node` classes +- Include OSGi metadata in jars (`META-INF/MANIFEST.MF` files) (#378) +- More documentation with examples for `Node` classes (#370) ### Changed - GitHub tables: Tables are now parsed even if there's no blank line before the - table heading, matching GitHub's behavior. + table heading, matching GitHub's behavior. (#381) ### Fixed - `MarkdownRenderer`: Fix precedence for `nodeRendererFactory`: Factories passed - to the builder can now override rendering for core node types. + to the builder can now override rendering for core node types. (#368) - `MarkdownRenderer`: Fix exception with ordered lists with a long first number followed by a shorter one (#382) -- Fix warning in Eclipse about "missing 'requires transitive'" -- Fix Android incompatibility with `requireNonNullElseGet` +- Fix warning in Eclipse about "missing 'requires transitive'" (#358) +- Fix Android incompatibility with `requireNonNullElseGet` (#369) ## [0.24.0] - 2024-10-21 ### Added From 3111fed6c73dc6e961c590c58973b44e12fe5464 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 1 Aug 2025 17:04:54 +1000 Subject: [PATCH 210/345] footnotes: Fix multiple paragraphs separated by blank lines --- CHANGELOG.md | 6 ++++++ .../internal/FootnoteBlockParser.java | 4 ++++ .../FootnoteMarkdownNodeRenderer.java | 2 -- .../FootnoteMarkdownRendererTest.java | 14 ++++++++++++- .../ext/footnotes/FootnotesTest.java | 20 +++++++++++++++++++ commonmark-integration-test/pom.xml | 4 ++++ .../commonmark/integration/Extensions.java | 2 ++ .../MarkdownRendererIntegrationTest.java | 12 ++--------- pom.xml | 5 +++++ 9 files changed, 56 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 346c0d6b0..8068d3c02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## Unreleased +### Fixed +- footnotes: Fix parsing of footnote definitions containing multiple paragraphs + separated by blank lines. Before it only worked if paragraphs were separated + by lines of 4 spaces. (#388) + ## [0.25.0] - 2025-06-20 ### Added - Include OSGi metadata in jars (`META-INF/MANIFEST.MF` files) (#378) diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java index 59ee5529e..110bdef20 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java @@ -39,6 +39,10 @@ public BlockContinue tryContinue(ParserState parserState) { if (parserState.getIndent() >= 4) { // It looks like content needs to be indented by 4 so that it's part of a footnote (instead of starting a new block). return BlockContinue.atColumn(4); + } else if (parserState.isBlank()) { + // A blank line doesn't finish a footnote yet. If there's another line with indent >= 4 after it, + // that should result in another paragraph in this footnote definition. + return BlockContinue.atIndex(parserState.getIndex()); } else { // We're not continuing to give other block parsers a chance to interrupt this definition. // But if no other block parser applied (including another FootnotesBlockParser), we will diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java index eddf6f2ea..3dcf4fc83 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java @@ -55,9 +55,7 @@ private void renderDefinition(FootnoteDefinition def) { writer.raw("]: "); writer.pushPrefix(" "); - writer.pushTight(true); renderChildren(def); - writer.popTight(); writer.popPrefix(); } diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java index be3af6715..2f1125a02 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java @@ -23,7 +23,7 @@ public void testSimple() { @Test public void testUnreferenced() { // Whether a reference has a corresponding definition or vice versa shouldn't matter for Markdown rendering. - assertRoundTrip("Test [^foo]\n\n[^foo]: one\n[^bar]: two\n"); + assertRoundTrip("Test [^foo]\n\n[^foo]: one\n\n[^bar]: two\n"); } @Test @@ -36,6 +36,18 @@ public void testBackslashInLabel() { assertRoundTrip("[^\\foo]\n\n[^\\foo]: note\n"); } + @Test + public void testMultipleLines() { + assertRoundTrip("Test [^1]\n\n[^1]: footnote l1\n footnote l2\n"); + } + + @Test + public void testMultipleParagraphs() { + // Note that the line between p1 and p2 could be blank too (instead of 4 spaces), but we currently don't + // preserve that information. + assertRoundTrip("Test [^1]\n\n[^1]: footnote p1\n \n footnote p2\n"); + } + @Test public void testInline() { assertRoundTrip("^[test *foo*]\n"); diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index 3fa726d8f..2477dbdf8 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -102,6 +102,26 @@ public void testDefContainsMultipleLines() { assertText("still", paragraph.getLastChild()); } + @Test + public void testDefContainsMultipleParagraphs() { + var doc = PARSER.parse("[^1]: footnote p1\n\n footnote p2\n"); + var def = find(doc, FootnoteDefinition.class); + assertThat(def.getLabel()).isEqualTo("1"); + var p1 = (Paragraph) def.getFirstChild(); + assertText("footnote p1", p1.getFirstChild()); + var p2 = (Paragraph) p1.getNext(); + assertText("footnote p2", p2.getFirstChild()); + } + + @Test + public void testDefFollowedByParagraph() { + var doc = PARSER.parse("[^1]: footnote\n\nnormal paragraph\n"); + var def = find(doc, FootnoteDefinition.class); + assertThat(def.getLabel()).isEqualTo("1"); + assertText("footnote", def.getFirstChild().getFirstChild()); + assertText("normal paragraph", def.getNext().getFirstChild()); + } + @Test public void testDefContainsList() { var doc = PARSER.parse("[^1]: - foo\n - bar\n"); diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index d5e108b0a..b495a85af 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -20,6 +20,10 @@ org.commonmark commonmark-ext-autolink + + org.commonmark + commonmark-ext-footnotes + org.commonmark commonmark-ext-ins diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java index ee7ee5290..8df0408cb 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java @@ -2,6 +2,7 @@ import org.commonmark.Extension; import org.commonmark.ext.autolink.AutolinkExtension; +import org.commonmark.ext.footnotes.FootnotesExtension; import org.commonmark.ext.front.matter.YamlFrontMatterExtension; import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; import org.commonmark.ext.gfm.tables.TablesExtension; @@ -15,6 +16,7 @@ public class Extensions { static final List ALL_EXTENSIONS = List.of( AutolinkExtension.create(), + FootnotesExtension.create(), ImageAttributesExtension.create(), InsExtension.create(), StrikethroughExtension.create(), diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java index 9ab5c32a3..fe14273ab 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java @@ -18,16 +18,8 @@ public class MarkdownRendererIntegrationTest { - private static final List EXTENSIONS = List.of( - AutolinkExtension.create(), - ImageAttributesExtension.create(), - InsExtension.create(), - StrikethroughExtension.create(), - TablesExtension.create(), - TaskListItemsExtension.create(), - YamlFrontMatterExtension.create()); - private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + private static final Parser PARSER = Parser.builder().extensions(Extensions.ALL_EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(Extensions.ALL_EXTENSIONS).build(); @Test public void testStrikethroughInTable() { diff --git a/pom.xml b/pom.xml index 6fd0c8df9..d036b1452 100644 --- a/pom.xml +++ b/pom.xml @@ -139,6 +139,11 @@ commonmark-ext-autolink 0.25.1-SNAPSHOT + + org.commonmark + commonmark-ext-footnotes + 0.25.1-SNAPSHOT + org.commonmark commonmark-ext-image-attributes From bd50c7d587c435c80eeb1f59d2c41fed3efefb33 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 1 Aug 2025 22:04:03 +1000 Subject: [PATCH 211/345] Prepare CHANGELOG for version 0.25.1 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8068d3c02..b04e11ad8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. -## Unreleased +## [0.25.1] - 2025-08-01 ### Fixed - footnotes: Fix parsing of footnote definitions containing multiple paragraphs separated by blank lines. Before it only worked if paragraphs were separated @@ -480,6 +480,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.25.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.0...commonmark-parent-0.25.1 [0.25.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.24.0...commonmark-parent-0.25.0 [0.24.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.23.0...commonmark-parent-0.24.0 [0.23.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.22.0...commonmark-parent-0.23.0 From b703d6a3561a6eb00f6de8dfab85cf0301cb7a2d Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Fri, 1 Aug 2025 12:07:57 +0000 Subject: [PATCH 212/345] [maven-release-plugin] prepare release commonmark-parent-0.25.1 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 26 ++++++++++++------------ 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index d0d86d572..a1f5f6eb6 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1-SNAPSHOT + 0.25.1 commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index d965c409e..a5979764e 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1-SNAPSHOT + 0.25.1 commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 05f290120..07a0a03d7 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1-SNAPSHOT + 0.25.1 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 6445eb34a..30df41254 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1-SNAPSHOT + 0.25.1 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index a1b137d73..8a87e9207 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1-SNAPSHOT + 0.25.1 commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 3f36b72bf..3e29fdd80 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1-SNAPSHOT + 0.25.1 commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 0c04f6311..af7ac7a54 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1-SNAPSHOT + 0.25.1 commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 051fc45bb..b9185381b 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1-SNAPSHOT + 0.25.1 commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 114d860c0..651c0e2f8 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.25.1-SNAPSHOT + 0.25.1 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index b495a85af..8300a25f7 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1-SNAPSHOT + 0.25.1 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index edad4a84d..58453821d 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1-SNAPSHOT + 0.25.1 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 22e9795db..a58676447 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1-SNAPSHOT + 0.25.1 commonmark diff --git a/pom.xml b/pom.xml index d036b1452..e02b49942 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.25.1-SNAPSHOT + 0.25.1 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,57 +132,57 @@ org.commonmark commonmark - 0.25.1-SNAPSHOT + 0.25.1 org.commonmark commonmark-ext-autolink - 0.25.1-SNAPSHOT + 0.25.1 org.commonmark commonmark-ext-footnotes - 0.25.1-SNAPSHOT + 0.25.1 org.commonmark commonmark-ext-image-attributes - 0.25.1-SNAPSHOT + 0.25.1 org.commonmark commonmark-ext-ins - 0.25.1-SNAPSHOT + 0.25.1 org.commonmark commonmark-ext-gfm-strikethrough - 0.25.1-SNAPSHOT + 0.25.1 org.commonmark commonmark-ext-gfm-tables - 0.25.1-SNAPSHOT + 0.25.1 org.commonmark commonmark-ext-heading-anchor - 0.25.1-SNAPSHOT + 0.25.1 org.commonmark commonmark-ext-task-list-items - 0.25.1-SNAPSHOT + 0.25.1 org.commonmark commonmark-ext-yaml-front-matter - 0.25.1-SNAPSHOT + 0.25.1 org.commonmark commonmark-test-util - 0.25.1-SNAPSHOT + 0.25.1 @@ -309,7 +309,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - HEAD + commonmark-parent-0.25.1 From 3c151053d720c99372d2d4c5c6db667135563ecf Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Fri, 1 Aug 2025 12:07:58 +0000 Subject: [PATCH 213/345] [maven-release-plugin] prepare for next development iteration --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 26 ++++++++++++------------ 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index a1f5f6eb6..360ad9781 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1 + 0.25.2-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index a5979764e..31a92ca77 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1 + 0.25.2-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 07a0a03d7..d051a0ee0 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1 + 0.25.2-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 30df41254..740460ada 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1 + 0.25.2-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 8a87e9207..1270cd1c6 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1 + 0.25.2-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 3e29fdd80..f392c8ea4 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1 + 0.25.2-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index af7ac7a54..1b797791f 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1 + 0.25.2-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index b9185381b..f3e626f89 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1 + 0.25.2-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 651c0e2f8..93d5e3e2c 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.25.1 + 0.25.2-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 8300a25f7..810919ce6 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1 + 0.25.2-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 58453821d..753bb00de 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1 + 0.25.2-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index a58676447..26edd4ffd 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.1 + 0.25.2-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index e02b49942..53289614e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.25.1 + 0.25.2-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,57 +132,57 @@ org.commonmark commonmark - 0.25.1 + 0.25.2-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.25.1 + 0.25.2-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.25.1 + 0.25.2-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.25.1 + 0.25.2-SNAPSHOT org.commonmark commonmark-ext-ins - 0.25.1 + 0.25.2-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.25.1 + 0.25.2-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.25.1 + 0.25.2-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.25.1 + 0.25.2-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.25.1 + 0.25.2-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.25.1 + 0.25.2-SNAPSHOT org.commonmark commonmark-test-util - 0.25.1 + 0.25.2-SNAPSHOT @@ -309,7 +309,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - commonmark-parent-0.25.1 + HEAD From 9926b761eb2e9192d80cda46917ea2f0f0f2c7dc Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 1 Aug 2025 22:27:12 +1000 Subject: [PATCH 214/345] README: Bump version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fa632a96d..62f5affe3 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.25.0 + 0.25.1 ``` @@ -291,7 +291,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.25.0 + 0.25.1 ``` From 70da759095d7da0cf39fbc2198250b718030652f Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Wed, 10 Sep 2025 09:08:15 -0700 Subject: [PATCH 215/345] Add Tests for Inline Alt Text --- .../java/org/commonmark/test/HtmlRendererTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 413bb9d8a..02d970949 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -279,6 +279,16 @@ public void imageAltTextWithEntities() { assertThat(defaultRenderer().render(parse("![foo ä](/url)\n"))).isEqualTo("

    \"foo

    \n"); } + @Test + public void imageAltTextWithInlines() { + assertThat(defaultRenderer().render(parse("![_foo_ **bar** [link](/url)](/url)\n"))).isEqualTo("

    \"foo

    \n"); + } + + @Test + public void imageAltTextWithCode() { + assertThat(defaultRenderer().render(parse("![`foo` bar](/url)\n"))).isEqualTo("

    \"foo

    \n"); + } + @Test public void canRenderContentsOfSingleParagraph() { Node paragraphs = parse("Here I have a test [link](http://www.google.com)"); From cc4740f42fc50c91a82b1d63fb6fc84df4039be7 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Wed, 10 Sep 2025 09:12:14 -0700 Subject: [PATCH 216/345] Add Visitor for Code Spans --- .../org/commonmark/renderer/html/CoreHtmlNodeRenderer.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java index 0603aa013..5c536558e 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -311,6 +311,11 @@ public void visit(Text text) { sb.append(text.getLiteral()); } + @Override + public void visit(Code code) { + sb.append(code.getLiteral()); + } + @Override public void visit(SoftLineBreak softLineBreak) { sb.append('\n'); From 77cadac10faa5cdccf0c8b4c0205d486d648f572 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Wed, 10 Sep 2025 09:20:33 -0700 Subject: [PATCH 217/345] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b04e11ad8..8020b3ce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## Unreleased +### Fixed + +- Fix rendering of image alt text to include contents of code spans (`` `code` ``). (#398) + ## [0.25.1] - 2025-08-01 ### Fixed - footnotes: Fix parsing of footnote definitions containing multiple paragraphs From 926013c7a626c4ed5ad5602de7eb7c3dfc4f3366 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 12 Sep 2025 22:50:47 +1000 Subject: [PATCH 218/345] Disallow link when a footnote is nested inside it Fixes #400. --- CHANGELOG.md | 9 +++++- .../ext/footnotes/FootnotesTest.java | 23 ++++++++++++++ .../commonmark/internal/InlineParserImpl.java | 31 +++++++++++++------ 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8020b3ce5..f41d904a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,16 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html with the exception that 0.x versions can break between minor versions. ## Unreleased +### Changed +- A `LinkProcessor` using `replaceWith` now also stops outer links from being + parsed as links, same as with `wrapTextIn`. This prevents nested links, see + footnotes change below. ### Fixed - - Fix rendering of image alt text to include contents of code spans (`` `code` ``). (#398) +- footnotes: Fix footnotes nested within links. Before, both the link and the + footnote reference would be parsed and lead to nested `` elements, which + is disallowed. Now, only the footnote is parsed and the outer link becomes + plain text; this matches the behavior of links. (#400) ## [0.25.1] - 2025-08-01 ### Fixed diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index 2477dbdf8..7763cedb4 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -254,6 +254,29 @@ public void testReferenceLinkWithoutDefinition() { assertText("[foo]", paragraph.getLastChild()); } + @Test + public void testFootnoteInLink() { + // Expected to behave the same way as a link within a link, see https://spec.commonmark.org/0.31.2/#example-518 + // i.e. the first (inner) link is parsed, which means the outer one becomes plain text, as nesting links is not + // allowed. + var doc = PARSER.parse("[link with footnote ref [^1]](https://example.com)\n\n[^1]: footnote\n"); + var ref = find(doc, FootnoteReference.class); + assertThat(ref.getLabel()).isEqualTo("1"); + var paragraph = doc.getFirstChild(); + assertText("[link with footnote ref ", paragraph.getFirstChild()); + assertText("](https://example.com)", paragraph.getLastChild()); + } + + @Test + public void testFootnoteWithMarkerInLink() { + var doc = PARSER.parse("[link with footnote ref ![^1]](https://example.com)\n\n[^1]: footnote\n"); + var ref = find(doc, FootnoteReference.class); + assertThat(ref.getLabel()).isEqualTo("1"); + var paragraph = doc.getFirstChild(); + assertText("[link with footnote ref !", paragraph.getFirstChild()); + assertText("](https://example.com)", paragraph.getLastChild()); + } + @Test public void testInlineFootnote() { var extension = FootnotesExtension.builder().inlineFootnotes(true).build(); diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 44f153e00..c4d9fc656 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -440,16 +440,9 @@ private Node wrapBracket(Bracket opener, Node wrapperNode, boolean includeMarker opener.bracketNode.unlink(); removeLastBracket(); - // Links within links are not allowed. We found this link, so there can be no other link around it. + // Links within links are not allowed. We found this link, so there can be no other links around it. if (opener.markerNode == null) { - Bracket bracket = lastBracket; - while (bracket != null) { - if (bracket.markerNode == null) { - // Disallow link opener. It will still get matched, but will not result in a link. - bracket.allowed = false; - } - bracket = bracket.previous; - } + disallowPreviousLinks(); } return wrapperNode; @@ -475,6 +468,15 @@ private Node replaceBracket(Bracket opener, Node node, boolean includeMarker) { n.unlink(); n = next; } + + // Links within links are not allowed. We found this link, so there can be no other links around it. + // Note that this makes any syntax like `[foo]` behave the same as built-in links, which is probably a good + // default (it works for footnotes). It might be useful for a `LinkProcessor` to be able to specify the + // behavior; something we could add to `LinkResult` in the future if requested. + if (opener.markerNode == null || !includeMarker) { + disallowPreviousLinks(); + } + return node; } @@ -489,6 +491,17 @@ private void removeLastBracket() { lastBracket = lastBracket.previous; } + private void disallowPreviousLinks() { + Bracket bracket = lastBracket; + while (bracket != null) { + if (bracket.markerNode == null) { + // Disallow link opener. It will still get matched, but will not result in a link. + bracket.allowed = false; + } + bracket = bracket.previous; + } + } + /** * Try to parse the destination and an optional title for an inline link/image. */ From c126ad3ba97a90cd2a1eed4b75881745ac896bff Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 13 Sep 2025 10:11:01 +1000 Subject: [PATCH 219/345] Add docs to ListItem and its appendChild about tight lists --- .../java/org/commonmark/node/ListItem.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/commonmark/src/main/java/org/commonmark/node/ListItem.java b/commonmark/src/main/java/org/commonmark/node/ListItem.java index e488e8fbe..c4d1214e7 100644 --- a/commonmark/src/main/java/org/commonmark/node/ListItem.java +++ b/commonmark/src/main/java/org/commonmark/node/ListItem.java @@ -2,6 +2,10 @@ /** * A child of a {@link ListBlock}, containing other blocks (e.g. {@link Paragraph}, other lists, etc). + *

    + * Note that a list item can't directly contain {@link Text}, it needs to be: + * {@link ListItem} : {@link Paragraph} : {@link Text}. + * If you want a list that is rendered tightly, create a list with {@link ListBlock#setTight(boolean)}. * * @see CommonMark Spec: List items */ @@ -57,4 +61,18 @@ public Integer getContentIndent() { public void setContentIndent(Integer contentIndent) { this.contentIndent = contentIndent; } + + /** + * @deprecated list items should only contain block nodes; if you're trying to create a list that is rendered + * without paragraphs, use {@link ListBlock#setTight(boolean)} instead. + */ + @Override + @Deprecated + public void appendChild(Node child) { + super.appendChild(child); + } + + public void appendChild(Block child) { + super.appendChild(child); + } } From 47e21c3237045bef6dd9dfda8691677fabf5e703 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 13 Sep 2025 10:25:24 +1000 Subject: [PATCH 220/345] Prepare CHANGELOG for version 0.26.0 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f41d904a9..c184b9ba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. -## Unreleased +## [0.26.0] - 2025-09-13 ### Changed - A `LinkProcessor` using `replaceWith` now also stops outer links from being parsed as links, same as with `wrapTextIn`. This prevents nested links, see @@ -492,6 +492,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.26.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.1...commonmark-parent-0.26.0 [0.25.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.0...commonmark-parent-0.25.1 [0.25.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.24.0...commonmark-parent-0.25.0 [0.24.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.23.0...commonmark-parent-0.24.0 From 729ba3fb04d28c856420f74aebf1b1e40df62fff Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 13 Sep 2025 10:25:45 +1000 Subject: [PATCH 221/345] Prepare for version 0.26.0 Ran `mvn versions:set -DnewVersion=0.26.0-SNAPSHOT` --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 24 ++++++++++++------------ 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 360ad9781..1295112c3 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 31a92ca77..c49312a04 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index d051a0ee0..c94d3a3e0 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 740460ada..2d89b1ea1 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 1270cd1c6..7e812d002 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index f392c8ea4..ef495cf45 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 1b797791f..15f9609e1 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index f3e626f89..70dc878ab 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 93d5e3e2c..46eb4b695 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 810919ce6..7cb3bf2cb 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 753bb00de..32ead5f20 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 26edd4ffd..518f711bb 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 53289614e..c322420ae 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,57 +132,57 @@ org.commonmark commonmark - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT org.commonmark commonmark-ext-ins - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT org.commonmark commonmark-test-util - 0.25.2-SNAPSHOT + 0.26.0-SNAPSHOT From 6874637cc60a219165259b857a07a0cad28b223a Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Sat, 13 Sep 2025 00:31:09 +0000 Subject: [PATCH 222/345] [maven-release-plugin] prepare release commonmark-parent-0.26.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 26 ++++++++++++------------ 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 1295112c3..810275c2c 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0-SNAPSHOT + 0.26.0 commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index c49312a04..af6e6b25c 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0-SNAPSHOT + 0.26.0 commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index c94d3a3e0..831d04666 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0-SNAPSHOT + 0.26.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 2d89b1ea1..0ee142010 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0-SNAPSHOT + 0.26.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 7e812d002..f4cf8ac9d 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0-SNAPSHOT + 0.26.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index ef495cf45..61617e1d2 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0-SNAPSHOT + 0.26.0 commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 15f9609e1..baad9b751 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0-SNAPSHOT + 0.26.0 commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 70dc878ab..ab20cb22e 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0-SNAPSHOT + 0.26.0 commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 46eb4b695..3fb85460c 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.26.0-SNAPSHOT + 0.26.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 7cb3bf2cb..d264e0b3a 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0-SNAPSHOT + 0.26.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 32ead5f20..344bbc447 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0-SNAPSHOT + 0.26.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 518f711bb..38a1c58e4 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0-SNAPSHOT + 0.26.0 commonmark diff --git a/pom.xml b/pom.xml index c322420ae..cb00d01bf 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.26.0-SNAPSHOT + 0.26.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,57 +132,57 @@ org.commonmark commonmark - 0.26.0-SNAPSHOT + 0.26.0 org.commonmark commonmark-ext-autolink - 0.26.0-SNAPSHOT + 0.26.0 org.commonmark commonmark-ext-footnotes - 0.26.0-SNAPSHOT + 0.26.0 org.commonmark commonmark-ext-image-attributes - 0.26.0-SNAPSHOT + 0.26.0 org.commonmark commonmark-ext-ins - 0.26.0-SNAPSHOT + 0.26.0 org.commonmark commonmark-ext-gfm-strikethrough - 0.26.0-SNAPSHOT + 0.26.0 org.commonmark commonmark-ext-gfm-tables - 0.26.0-SNAPSHOT + 0.26.0 org.commonmark commonmark-ext-heading-anchor - 0.26.0-SNAPSHOT + 0.26.0 org.commonmark commonmark-ext-task-list-items - 0.26.0-SNAPSHOT + 0.26.0 org.commonmark commonmark-ext-yaml-front-matter - 0.26.0-SNAPSHOT + 0.26.0 org.commonmark commonmark-test-util - 0.26.0-SNAPSHOT + 0.26.0 @@ -309,7 +309,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - HEAD + commonmark-parent-0.26.0 From 868e006d03158c814bc51ef2c0cbb6cbe0f7e23c Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Sat, 13 Sep 2025 00:31:10 +0000 Subject: [PATCH 223/345] [maven-release-plugin] prepare for next development iteration --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 26 ++++++++++++------------ 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 810275c2c..5bb143122 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0 + 0.26.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index af6e6b25c..4947e33b9 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0 + 0.26.1-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 831d04666..37216c971 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0 + 0.26.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 0ee142010..8acc77a5d 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0 + 0.26.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index f4cf8ac9d..b0207beeb 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0 + 0.26.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 61617e1d2..89efac64f 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0 + 0.26.1-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index baad9b751..03be87d08 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0 + 0.26.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index ab20cb22e..e772677c5 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0 + 0.26.1-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 3fb85460c..b39f253ec 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.26.0 + 0.26.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index d264e0b3a..3ca1d58da 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0 + 0.26.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 344bbc447..f7c405069 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0 + 0.26.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 38a1c58e4..fb5ddb08c 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.0 + 0.26.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index cb00d01bf..926e275c9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.26.0 + 0.26.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,57 +132,57 @@ org.commonmark commonmark - 0.26.0 + 0.26.1-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.26.0 + 0.26.1-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.26.0 + 0.26.1-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.26.0 + 0.26.1-SNAPSHOT org.commonmark commonmark-ext-ins - 0.26.0 + 0.26.1-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.26.0 + 0.26.1-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.26.0 + 0.26.1-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.26.0 + 0.26.1-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.26.0 + 0.26.1-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.26.0 + 0.26.1-SNAPSHOT org.commonmark commonmark-test-util - 0.26.0 + 0.26.1-SNAPSHOT @@ -309,7 +309,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - commonmark-parent-0.26.0 + HEAD From 44ebb2e07d71ff3a62ac876d40fe03d2c07b8fd6 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 13 Sep 2025 10:41:07 +1000 Subject: [PATCH 224/345] README: Bump version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 62f5affe3..79c2a4923 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.25.1 + 0.26.0 ``` @@ -291,7 +291,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.25.1 + 0.26.0 ``` From ddf81a0dd83e72631c434620a3005c999befa0e6 Mon Sep 17 00:00:00 2001 From: Sebastian Thomschke Date: Sat, 20 Sep 2025 00:39:22 +0200 Subject: [PATCH 225/345] README: Add Used By Eclipse Previewer Plugin --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 79c2a4923..c61c7bdc0 100644 --- a/README.md +++ b/README.md @@ -453,6 +453,7 @@ Some users of this library (feel free to raise a PR if you want to be added): * [Open Note](https://github.com/YangDai2003/OpenNote-Compose) a markdown editor and note-taking app for Android * [Quarkus Roq](https://github.com/quarkiverse/quarkus-roq/) The Roq Static Site Generator allows to easily create a static website or blog using Quarkus super-powers. * [Lucee](https://github.com/lucee/lucee) +* [Previewer](https://github.com/sebthom/previewer-eclipse-plugin) an extensible Eclipse plugin that previews Markdown and other text based formats. See also -------- From 3208a83f640680586c31087612406cfbfc12e4df Mon Sep 17 00:00:00 2001 From: rdestefa <360fanelite@gmail.com> Date: Fri, 3 Oct 2025 21:57:52 -0700 Subject: [PATCH 226/345] Allow Detection of WWW Autolinks --- .../ext/autolink/AutolinkExtension.java | 68 ++++++++++++++++++- .../commonmark/ext/autolink/AutolinkType.java | 16 +++++ .../internal/AutolinkPostProcessor.java | 44 ++++++++++-- .../commonmark/ext/autolink/AutolinkTest.java | 18 +++++ 4 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkType.java diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java index e5926c7bb..115676295 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java @@ -1,5 +1,8 @@ package org.commonmark.ext.autolink; +import java.util.EnumSet; +import java.util.Set; + import org.commonmark.Extension; import org.commonmark.ext.autolink.internal.AutolinkPostProcessor; import org.commonmark.parser.Parser; @@ -18,16 +21,75 @@ */ public class AutolinkExtension implements Parser.ParserExtension { - private AutolinkExtension() { + private final Set linkTypes; + + private AutolinkExtension(Builder builder) { + this.linkTypes = builder.linkTypes; } + /** + * @return the extension with default options + */ public static Extension create() { - return new AutolinkExtension(); + return builder().build(); + } + + /** + * @return a builder to configure the behavior of the extension. + */ + public static Builder builder() { + return new Builder(); } @Override public void extend(Parser.Builder parserBuilder) { - parserBuilder.postProcessor(new AutolinkPostProcessor()); + parserBuilder.postProcessor(new AutolinkPostProcessor(linkTypes)); } + public static class Builder { + + private Set linkTypes = EnumSet.of(AutolinkType.URL, AutolinkType.EMAIL); + + /** + * @param linkTypes the link types that should be converted. By default, {@link AutolinkType#URL} + * and {@link AutolinkType#EMAIL} are converted. + * @return {@code this} + */ + public Builder linkTypes(AutolinkType... linkTypes) { + if (linkTypes == null) { + throw new NullPointerException("linkTypes must not be null"); + } + + if (linkTypes.length == 0) { + throw new IllegalArgumentException("linkTypes must not be empty"); + } + + return this.linkTypes(Set.of(linkTypes)); + } + + /** + * @param linkTypes the link types that should be converted. By default, {@link AutolinkType#URL} + * and {@link AutolinkType#EMAIL} are converted. + * @return {@code this} + */ + public Builder linkTypes(Set linkTypes) { + if (linkTypes == null) { + throw new NullPointerException("linkTypes must not be null"); + } + + if (linkTypes.isEmpty()) { + throw new IllegalArgumentException("linkTypes must not be empty"); + } + + this.linkTypes = EnumSet.copyOf(linkTypes); + return this; + } + + /** + * @return a configured extension + */ + public Extension build() { + return new AutolinkExtension(this); + } + } } diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkType.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkType.java new file mode 100644 index 000000000..3e397a42f --- /dev/null +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkType.java @@ -0,0 +1,16 @@ +package org.commonmark.ext.autolink; + +public enum AutolinkType { + /** + * URL such as {@code http://example.com} + */ + URL, + /** + * Email address such as {@code foo@example.com} + */ + EMAIL, + /** + * URL such as {@code www.example.com} + */ + WWW +} diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java index ee8847911..33f70bf21 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java @@ -1,5 +1,6 @@ package org.commonmark.ext.autolink.internal; +import org.commonmark.ext.autolink.AutolinkType; import org.commonmark.node.*; import org.commonmark.parser.PostProcessor; import org.nibor.autolink.LinkExtractor; @@ -11,9 +12,40 @@ public class AutolinkPostProcessor implements PostProcessor { - private LinkExtractor linkExtractor = LinkExtractor.builder() - .linkTypes(EnumSet.of(LinkType.URL, LinkType.EMAIL)) - .build(); + private final LinkExtractor linkExtractor; + + public AutolinkPostProcessor() { + this(EnumSet.of(AutolinkType.URL, AutolinkType.EMAIL)); + } + + public AutolinkPostProcessor(Set linkTypes) { + if (linkTypes == null) { + throw new NullPointerException("linkTypes must not be null"); + } + + if (linkTypes.isEmpty()) { + throw new IllegalArgumentException("linkTypes must not be empty"); + } + + EnumSet types = EnumSet.noneOf(LinkType.class); + for (AutolinkType linkType : linkTypes) { + switch (linkType) { + case URL: + types.add(LinkType.URL); + break; + case EMAIL: + types.add(LinkType.EMAIL); + break; + case WWW: + types.add(LinkType.WWW); + break; + } + } + + this.linkExtractor = LinkExtractor.builder() + .linkTypes(types) + .build(); + } @Override public Node process(Node node) { @@ -67,8 +99,12 @@ private static Text createTextNode(String literal, Span span, SourceSpan sourceS } private static String getDestination(LinkSpan linkSpan, String linkText) { - if (linkSpan.getType() == LinkType.EMAIL) { + LinkType type = linkSpan.getType(); + + if (type == LinkType.EMAIL) { return "mailto:" + linkText; + } else if (type == LinkType.WWW) { + return "http://" + linkText; } else { return linkText; } diff --git a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java index 338513f33..81d898a31 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -19,6 +19,12 @@ public class AutolinkTest extends RenderingTestCase { private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final Set WWW_EXTENSIONS = Set.of(AutolinkExtension.builder() + .linkTypes(AutolinkType.URL, AutolinkType.EMAIL, AutolinkType.WWW) + .build()); + private static final Parser WWW_PARSER = Parser.builder().extensions(WWW_EXTENSIONS).build(); + private static final HtmlRenderer WWW_RENDERER = HtmlRenderer.builder().extensions(WWW_EXTENSIONS).build(); + @Test public void oneTextNode() { assertRendering("foo http://one.org/ bar http://two.org/", @@ -57,6 +63,18 @@ public void dontLinkTextWithinLinks() { "

    http://example.com

    \n"); } + @Test + public void wwwLinksDontWorkByDefault() { + assertRendering("www.example.com", + "

    www.example.com

    \n"); + } + + @Test + public void wwwLinks() { + String html = WWW_RENDERER.render(WWW_PARSER.parse("www.example.com")); + assertThat(html).isEqualTo("

    www.example.com

    \n"); + } + @Test public void sourceSpans() { Parser parser = Parser.builder() From 675d2c45805a1089ea3f1bd78f0d3864fecfb78f Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Mon, 6 Oct 2025 01:55:58 -0700 Subject: [PATCH 227/345] Address PR Comments --- .../org/commonmark/ext/autolink/AutolinkExtension.java | 4 ---- .../java/org/commonmark/ext/autolink/AutolinkType.java | 3 +++ .../ext/autolink/internal/AutolinkPostProcessor.java | 10 +++------- .../java/org/commonmark/ext/autolink/AutolinkTest.java | 2 +- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java index 115676295..99c4f6d40 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java @@ -60,10 +60,6 @@ public Builder linkTypes(AutolinkType... linkTypes) { throw new NullPointerException("linkTypes must not be null"); } - if (linkTypes.length == 0) { - throw new IllegalArgumentException("linkTypes must not be empty"); - } - return this.linkTypes(Set.of(linkTypes)); } diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkType.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkType.java index 3e397a42f..2c8c6574f 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkType.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkType.java @@ -1,5 +1,8 @@ package org.commonmark.ext.autolink; +/** + * The types of strings that can be automatically turned into links. + */ public enum AutolinkType { /** * URL such as {@code http://example.com} diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java index 33f70bf21..4ce070fd8 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java @@ -14,10 +14,6 @@ public class AutolinkPostProcessor implements PostProcessor { private final LinkExtractor linkExtractor; - public AutolinkPostProcessor() { - this(EnumSet.of(AutolinkType.URL, AutolinkType.EMAIL)); - } - public AutolinkPostProcessor(Set linkTypes) { if (linkTypes == null) { throw new NullPointerException("linkTypes must not be null"); @@ -27,7 +23,7 @@ public AutolinkPostProcessor(Set linkTypes) { throw new IllegalArgumentException("linkTypes must not be empty"); } - EnumSet types = EnumSet.noneOf(LinkType.class); + var types = EnumSet.noneOf(LinkType.class); for (AutolinkType linkType : linkTypes) { switch (linkType) { case URL: @@ -99,12 +95,12 @@ private static Text createTextNode(String literal, Span span, SourceSpan sourceS } private static String getDestination(LinkSpan linkSpan, String linkText) { - LinkType type = linkSpan.getType(); + var type = linkSpan.getType(); if (type == LinkType.EMAIL) { return "mailto:" + linkText; } else if (type == LinkType.WWW) { - return "http://" + linkText; + return "https://" + linkText; } else { return linkText; } diff --git a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java index 81d898a31..ada6c1580 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -72,7 +72,7 @@ public void wwwLinksDontWorkByDefault() { @Test public void wwwLinks() { String html = WWW_RENDERER.render(WWW_PARSER.parse("www.example.com")); - assertThat(html).isEqualTo("

    www.example.com

    \n"); + assertThat(html).isEqualTo("

    www.example.com

    \n"); } @Test From 4186529fe603de45fd68003aef0f78354f09879a Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Mon, 6 Oct 2025 02:13:32 -0700 Subject: [PATCH 228/345] Add Changelog Entry --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c184b9ba6..5de44520a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## Unreleased +### Added +- Autolink extension: Now supports configuration of different link types that + should be recognized and converted to links. See `AutolinkExtension#builder` + | Type | Default? | Description | + | `URL` | Yes | URL with a protocol such as `https://example.com` | + | `EMAIL` | Yes | Email address such as `foo@example.com` | + | `WWW` | No | An address beginning with `www` such as `www.example.com` | + ## [0.26.0] - 2025-09-13 ### Changed - A `LinkProcessor` using `replaceWith` now also stops outer links from being From 64f76d9727682af819c9492a636040e8fcb6a4aa Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Mon, 6 Oct 2025 02:15:12 -0700 Subject: [PATCH 229/345] Fix Changelog Typo --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de44520a..bed5058b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ with the exception that 0.x versions can break between minor versions. - Autolink extension: Now supports configuration of different link types that should be recognized and converted to links. See `AutolinkExtension#builder` | Type | Default? | Description | + |---------|----------|-----------------------------------------------------------| | `URL` | Yes | URL with a protocol such as `https://example.com` | | `EMAIL` | Yes | Email address such as `foo@example.com` | | `WWW` | No | An address beginning with `www` such as `www.example.com` | From e49094cf8e0912b79737e6fed66a8e245b8285e0 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:10:13 -0700 Subject: [PATCH 230/345] Address PR Comments 2 --- CHANGELOG.md | 20 ++++++++++++++----- .../ext/autolink/AutolinkExtension.java | 10 +++++----- .../internal/AutolinkPostProcessor.java | 3 ++- .../commonmark/ext/autolink/AutolinkTest.java | 18 ++++++++--------- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bed5058b3..74989fe2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,21 @@ with the exception that 0.x versions can break between minor versions. ### Added - Autolink extension: Now supports configuration of different link types that should be recognized and converted to links. See `AutolinkExtension#builder` - | Type | Default? | Description | - |---------|----------|-----------------------------------------------------------| - | `URL` | Yes | URL with a protocol such as `https://example.com` | - | `EMAIL` | Yes | Email address such as `foo@example.com` | - | `WWW` | No | An address beginning with `www` such as `www.example.com` | + + | Type | Default? | Description | + |---------|----------|--------------------------------------------------------| + | `URL` | Yes | URL with a protocol such as `https://example.com` | + | `EMAIL` | Yes | Email address such as `foo@example.com` | + | `WWW` | Yes | Address beginning with `www` such as `www.example.com` | + + > [!NOTE] + > + > This changes the behavior of `AutolinkExtension.create()` to now also include + > `WWW` links by default. To re-enable the previous behavior, use: + > + > ```java + > AutolinkExtension.builder().linkTypes(AutolinkType.URL, AutolinkType.EMAIL).build(); + > ``` ## [0.26.0] - 2025-09-13 ### Changed diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java index 99c4f6d40..7d5a74f30 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java @@ -48,11 +48,11 @@ public void extend(Parser.Builder parserBuilder) { public static class Builder { - private Set linkTypes = EnumSet.of(AutolinkType.URL, AutolinkType.EMAIL); + private Set linkTypes = EnumSet.allOf(AutolinkType.class); /** - * @param linkTypes the link types that should be converted. By default, {@link AutolinkType#URL} - * and {@link AutolinkType#EMAIL} are converted. + * @param linkTypes the link types that should be converted. By default, + * all {@link AutolinkType}s are converted. * @return {@code this} */ public Builder linkTypes(AutolinkType... linkTypes) { @@ -64,8 +64,8 @@ public Builder linkTypes(AutolinkType... linkTypes) { } /** - * @param linkTypes the link types that should be converted. By default, {@link AutolinkType#URL} - * and {@link AutolinkType#EMAIL} are converted. + * @param linkTypes the link types that should be converted. By default, + * all {@link AutolinkType}s are converted. * @return {@code this} */ public Builder linkTypes(Set linkTypes) { diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java index 4ce070fd8..a381c2f19 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java @@ -100,7 +100,8 @@ private static String getDestination(LinkSpan linkSpan, String linkText) { if (type == LinkType.EMAIL) { return "mailto:" + linkText; } else if (type == LinkType.WWW) { - return "https://" + linkText; + // Use http instead of https (see https://github.github.com/gfm/#extended-www-autolink) + return "http://" + linkText; } else { return linkText; } diff --git a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java index ada6c1580..1b93cb10c 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -19,11 +19,11 @@ public class AutolinkTest extends RenderingTestCase { private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); - private static final Set WWW_EXTENSIONS = Set.of(AutolinkExtension.builder() - .linkTypes(AutolinkType.URL, AutolinkType.EMAIL, AutolinkType.WWW) + private static final Set NO_WWW_EXTENSIONS = Set.of(AutolinkExtension.builder() + .linkTypes(AutolinkType.URL, AutolinkType.EMAIL) .build()); - private static final Parser WWW_PARSER = Parser.builder().extensions(WWW_EXTENSIONS).build(); - private static final HtmlRenderer WWW_RENDERER = HtmlRenderer.builder().extensions(WWW_EXTENSIONS).build(); + private static final Parser NO_WWW_PARSER = Parser.builder().extensions(NO_WWW_EXTENSIONS).build(); + private static final HtmlRenderer NO_WWW_RENDERER = HtmlRenderer.builder().extensions(NO_WWW_EXTENSIONS).build(); @Test public void oneTextNode() { @@ -64,15 +64,15 @@ public void dontLinkTextWithinLinks() { } @Test - public void wwwLinksDontWorkByDefault() { + public void wwwLinks() { assertRendering("www.example.com", - "

    www.example.com

    \n"); + "

    www.example.com

    \n"); } @Test - public void wwwLinks() { - String html = WWW_RENDERER.render(WWW_PARSER.parse("www.example.com")); - assertThat(html).isEqualTo("

    www.example.com

    \n"); + public void noWwwLinks() { + String html = NO_WWW_RENDERER.render(NO_WWW_PARSER.parse("www.example.com")); + assertThat(html).isEqualTo("

    www.example.com

    \n"); } @Test From 53bf47d5cf6e7854dee03af658b1f7960c585dcf Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:17:58 -0700 Subject: [PATCH 231/345] Fix Test Failure --- CHANGELOG.md | 14 ++++++-------- .../org/commonmark/ext/autolink/AutolinkTest.java | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74989fe2b..40945d940 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,15 +16,13 @@ with the exception that 0.x versions can break between minor versions. | `URL` | Yes | URL with a protocol such as `https://example.com` | | `EMAIL` | Yes | Email address such as `foo@example.com` | | `WWW` | Yes | Address beginning with `www` such as `www.example.com` | + + Note that This changes the behavior of `AutolinkExtension.create()` to now also + include `WWW` links by default. To re-enable the previous behavior, use: - > [!NOTE] - > - > This changes the behavior of `AutolinkExtension.create()` to now also include - > `WWW` links by default. To re-enable the previous behavior, use: - > - > ```java - > AutolinkExtension.builder().linkTypes(AutolinkType.URL, AutolinkType.EMAIL).build(); - > ``` + ```java + AutolinkExtension.builder().linkTypes(AutolinkType.URL, AutolinkType.EMAIL).build(); + ``` ## [0.26.0] - 2025-09-13 ### Changed diff --git a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java index 1b93cb10c..82c3899fc 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -66,7 +66,7 @@ public void dontLinkTextWithinLinks() { @Test public void wwwLinks() { assertRendering("www.example.com", - "

    www.example.com

    \n"); + "

    www.example.com

    \n"); } @Test From 889709c57578369685f4ece88917c1ffa8df44c2 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:20:40 -0700 Subject: [PATCH 232/345] Typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40945d940..9cb1bae6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ with the exception that 0.x versions can break between minor versions. | `EMAIL` | Yes | Email address such as `foo@example.com` | | `WWW` | Yes | Address beginning with `www` such as `www.example.com` | - Note that This changes the behavior of `AutolinkExtension.create()` to now also + Note that this changes the behavior of `AutolinkExtension.create()` to now also include `WWW` links by default. To re-enable the previous behavior, use: ```java From 76b2168d9b8ad2b186cb05e1d985a37629b32318 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 12 Oct 2025 20:59:44 +1100 Subject: [PATCH 233/345] Prepare CHANGELOG for version 0.27.0 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cb1bae6b..31499759a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. -## Unreleased +## [0.27.0] - 2025-10-12 ### Added - Autolink extension: Now supports configuration of different link types that should be recognized and converted to links. See `AutolinkExtension#builder` @@ -510,6 +510,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.27.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.26.0...commonmark-parent-0.27.0 [0.26.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.1...commonmark-parent-0.26.0 [0.25.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.0...commonmark-parent-0.25.1 [0.25.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.24.0...commonmark-parent-0.25.0 From c55b0ecf5655d7265dfc60dbf454d1d78282b84a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 12 Oct 2025 21:00:33 +1100 Subject: [PATCH 234/345] Prepare for version 0.27.0 Ran `mvn versions:set -DnewVersion=0.27.0-SNAPSHOT` --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 24 ++++++++++++------------ 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 5bb143122..4dd71fb86 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 4947e33b9..f6c7a654b 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 37216c971..78803d540 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 8acc77a5d..c8c54bd5b 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index b0207beeb..832a1a3f3 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 89efac64f..190daf471 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 03be87d08..6ed456a8b 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index e772677c5..4e5181ac2 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index b39f253ec..8341ee316 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 3ca1d58da..38573a3ee 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index f7c405069..8366b07b9 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index fb5ddb08c..cf242faf5 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 926e275c9..235d1ab01 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,57 +132,57 @@ org.commonmark commonmark - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT org.commonmark commonmark-ext-ins - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT org.commonmark commonmark-test-util - 0.26.1-SNAPSHOT + 0.27.0-SNAPSHOT From de91f05a96061f5afe5b2c107610e27c1e9c2703 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 12 Oct 2025 21:03:24 +1100 Subject: [PATCH 235/345] Test on Java 25 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e94dabbba..b32794271 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [11, 17, 21, 24] + java: [11, 17, 21, 25] steps: - name: Checkout sources uses: actions/checkout@v4 From 70f6819d263a506ea20f4d0fb18631ca21bab2e6 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Sun, 12 Oct 2025 10:12:46 +0000 Subject: [PATCH 236/345] [maven-release-plugin] prepare release commonmark-parent-0.27.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 26 ++++++++++++------------ 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 4dd71fb86..7bbfbf99a 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0-SNAPSHOT + 0.27.0 commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index f6c7a654b..6aa86a6f8 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0-SNAPSHOT + 0.27.0 commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 78803d540..d8d8c3397 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0-SNAPSHOT + 0.27.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index c8c54bd5b..98dfac272 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0-SNAPSHOT + 0.27.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 832a1a3f3..a3883ed73 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0-SNAPSHOT + 0.27.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 190daf471..7f7ce5002 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0-SNAPSHOT + 0.27.0 commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 6ed456a8b..2e8790c4a 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0-SNAPSHOT + 0.27.0 commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 4e5181ac2..323723d59 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0-SNAPSHOT + 0.27.0 commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 8341ee316..e08948c6c 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.27.0-SNAPSHOT + 0.27.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 38573a3ee..18d9f0394 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0-SNAPSHOT + 0.27.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 8366b07b9..e0e0e97f8 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0-SNAPSHOT + 0.27.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index cf242faf5..2528b4074 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0-SNAPSHOT + 0.27.0 commonmark diff --git a/pom.xml b/pom.xml index 235d1ab01..9ce4f2b28 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.27.0-SNAPSHOT + 0.27.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,57 +132,57 @@ org.commonmark commonmark - 0.27.0-SNAPSHOT + 0.27.0 org.commonmark commonmark-ext-autolink - 0.27.0-SNAPSHOT + 0.27.0 org.commonmark commonmark-ext-footnotes - 0.27.0-SNAPSHOT + 0.27.0 org.commonmark commonmark-ext-image-attributes - 0.27.0-SNAPSHOT + 0.27.0 org.commonmark commonmark-ext-ins - 0.27.0-SNAPSHOT + 0.27.0 org.commonmark commonmark-ext-gfm-strikethrough - 0.27.0-SNAPSHOT + 0.27.0 org.commonmark commonmark-ext-gfm-tables - 0.27.0-SNAPSHOT + 0.27.0 org.commonmark commonmark-ext-heading-anchor - 0.27.0-SNAPSHOT + 0.27.0 org.commonmark commonmark-ext-task-list-items - 0.27.0-SNAPSHOT + 0.27.0 org.commonmark commonmark-ext-yaml-front-matter - 0.27.0-SNAPSHOT + 0.27.0 org.commonmark commonmark-test-util - 0.27.0-SNAPSHOT + 0.27.0 @@ -309,7 +309,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - HEAD + commonmark-parent-0.27.0 From f4f327084d298145b714c1cef6d1ccaf355f6ab8 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Sun, 12 Oct 2025 10:12:48 +0000 Subject: [PATCH 237/345] [maven-release-plugin] prepare for next development iteration --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 26 ++++++++++++------------ 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 7bbfbf99a..5b6989097 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0 + 0.27.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 6aa86a6f8..09d962e41 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0 + 0.27.1-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index d8d8c3397..77bea3f36 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0 + 0.27.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 98dfac272..00dd10420 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0 + 0.27.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index a3883ed73..f3ede6535 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0 + 0.27.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 7f7ce5002..384bd785f 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0 + 0.27.1-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 2e8790c4a..d9789dfe6 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0 + 0.27.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 323723d59..bbedcf976 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0 + 0.27.1-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index e08948c6c..b4aea9395 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.27.0 + 0.27.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 18d9f0394..0f9f3b73b 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0 + 0.27.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index e0e0e97f8..016bd600b 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0 + 0.27.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 2528b4074..e5983e81a 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.0 + 0.27.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 9ce4f2b28..7818b0ba4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.27.0 + 0.27.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,57 +132,57 @@ org.commonmark commonmark - 0.27.0 + 0.27.1-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.27.0 + 0.27.1-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.27.0 + 0.27.1-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.27.0 + 0.27.1-SNAPSHOT org.commonmark commonmark-ext-ins - 0.27.0 + 0.27.1-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.27.0 + 0.27.1-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.27.0 + 0.27.1-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.27.0 + 0.27.1-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.27.0 + 0.27.1-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.27.0 + 0.27.1-SNAPSHOT org.commonmark commonmark-test-util - 0.27.0 + 0.27.1-SNAPSHOT @@ -309,7 +309,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - commonmark-parent-0.27.0 + HEAD From 5fa3b86044b577cd826a3bc2c89a2a644f23061c Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 12 Oct 2025 21:27:24 +1100 Subject: [PATCH 238/345] README: Bump version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c61c7bdc0..2e5aea22c 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.26.0 + 0.27.0 ``` @@ -291,7 +291,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.26.0 + 0.27.0 ``` From 7c0d35a5e175d2d4afad9c3c783a7cb141cbf13f Mon Sep 17 00:00:00 2001 From: David Gerber Date: Sun, 12 Oct 2025 22:14:53 +0200 Subject: [PATCH 239/345] Add library user to the README Xeres is a Peer-to-Peer application that uses commonmark-java --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e5aea22c..702cc3abc 100644 --- a/README.md +++ b/README.md @@ -453,7 +453,8 @@ Some users of this library (feel free to raise a PR if you want to be added): * [Open Note](https://github.com/YangDai2003/OpenNote-Compose) a markdown editor and note-taking app for Android * [Quarkus Roq](https://github.com/quarkiverse/quarkus-roq/) The Roq Static Site Generator allows to easily create a static website or blog using Quarkus super-powers. * [Lucee](https://github.com/lucee/lucee) -* [Previewer](https://github.com/sebthom/previewer-eclipse-plugin) an extensible Eclipse plugin that previews Markdown and other text based formats. +* [Previewer](https://github.com/sebthom/previewer-eclipse-plugin) an extensible Eclipse plugin that previews Markdown and other text based formats. +* [Xeres](https://xeres.io) a Peer-to-Peer application where all user generated content is done with markdown See also -------- From fd334350cb1d32fe745f50ca17c6096642ccbab2 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 13 Jan 2026 17:26:18 +0100 Subject: [PATCH 240/345] Fix line break parsing after hard line break Fixes #415. --- CHANGELOG.md | 6 ++++++ .../org/commonmark/internal/InlineParserImpl.java | 4 +++- .../commonmark/test/CoreRenderingTestCase.java | 3 ++- .../org/commonmark/test/SpecialInputTest.java | 15 +++++++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31499759a..e194b9b74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## [Unreleased] +### Fixed +- Line(s) after a hard line break would sometimes also get an unwanted hard + line break, e.g. if they ended in emphasis or other non-text inlines (#415) + ## [0.27.0] - 2025-10-12 ### Added - Autolink extension: Now supports configuration of different link types that @@ -510,6 +515,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[Unreleased]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.0...main [0.27.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.26.0...commonmark-parent-0.27.0 [0.26.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.1...commonmark-parent-0.26.0 [0.25.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.0...commonmark-parent-0.25.1 diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index c4d9fc656..fef30c0f7 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -598,7 +598,9 @@ static String parseLinkLabel(Scanner scanner) { private Node parseLineBreak() { scanner.next(); - if (trailingSpaces >= 2) { + var hard = trailingSpaces >= 2; + trailingSpaces = 0; + if (hard) { return new HardLineBreak(); } else { return new SoftLineBreak(); diff --git a/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java b/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java index 38f319e1c..2303d2617 100644 --- a/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java +++ b/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java @@ -11,6 +11,7 @@ public class CoreRenderingTestCase extends RenderingTestCase { @Override protected String render(String source) { - return RENDERER.render(PARSER.parse(source)); + var node = PARSER.parse(source); + return RENDERER.render(node); } } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 2ebac1711..45cd3aea2 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -210,4 +210,19 @@ public void htmlBlockInterruptingList() { "\n" + "\n"); } + + @Test + public void emphasisAfterHardLineBreak() { + assertRendering("Hello \n" + + "**Bar**\n" + + "Foo\n", "

    Hello
    \n" + + "Bar\n" + + "Foo

    \n"); + + assertRendering("Hello \n" + + "**Bar** \n" + + "Foo\n", "

    Hello
    \n" + + "Bar
    \n" + + "Foo

    \n"); + } } From 2753bf2b36f5048be2125d5a5bd2ef71ee35a79b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 14 Jan 2026 14:21:56 +0100 Subject: [PATCH 241/345] TextContentRenderer: Fix nested lists on the same line --- CHANGELOG.md | 1 + .../internal/renderer/text/ListHolder.java | 14 ------ .../text/CoreTextContentNodeRenderer.java | 28 ++++++++++-- .../renderer/text/TextContentWriter.java | 44 ++++++++++++++++++- .../test/TextContentRendererTest.java | 23 +++++++--- 5 files changed, 85 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e194b9b74..d091306ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ with the exception that 0.x versions can break between minor versions. ### Fixed - Line(s) after a hard line break would sometimes also get an unwanted hard line break, e.g. if they ended in emphasis or other non-text inlines (#415) +- `TextContentRenderer` (for plain text): Fix nested lists on the same line (#413) ## [0.27.0] - 2025-10-12 ### Added diff --git a/commonmark/src/main/java/org/commonmark/internal/renderer/text/ListHolder.java b/commonmark/src/main/java/org/commonmark/internal/renderer/text/ListHolder.java index cb06d4a9d..14ca5b9af 100644 --- a/commonmark/src/main/java/org/commonmark/internal/renderer/text/ListHolder.java +++ b/commonmark/src/main/java/org/commonmark/internal/renderer/text/ListHolder.java @@ -1,27 +1,13 @@ package org.commonmark.internal.renderer.text; public abstract class ListHolder { - private static final String INDENT_DEFAULT = " "; - private static final String INDENT_EMPTY = ""; - private final ListHolder parent; - private final String indent; ListHolder(ListHolder parent) { this.parent = parent; - - if (parent != null) { - indent = parent.indent + INDENT_DEFAULT; - } else { - indent = INDENT_EMPTY; - } } public ListHolder getParent() { return parent; } - - public String getIndent() { - return indent; - } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java index 68b1fbce5..2a27571c6 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -161,19 +161,30 @@ public void visit(Link link) { @Override public void visit(ListItem listItem) { if (listHolder != null && listHolder instanceof OrderedListHolder) { - OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; - String indent = stripNewlines() ? "" : orderedListHolder.getIndent(); - textContent.write(indent + orderedListHolder.getCounter() + orderedListHolder.getDelimiter() + " "); + var orderedListHolder = (OrderedListHolder) listHolder; + var marker = orderedListHolder.getCounter() + orderedListHolder.getDelimiter(); + var spaces = " "; + textContent.write(marker); + textContent.write(spaces); + textContent.pushPrefix(repeat(" ", marker.length() + spaces.length())); visitChildren(listItem); textContent.block(); + textContent.popPrefix(); orderedListHolder.increaseCounter(); } else if (listHolder != null && listHolder instanceof BulletListHolder) { BulletListHolder bulletListHolder = (BulletListHolder) listHolder; if (!stripNewlines()) { - textContent.write(bulletListHolder.getIndent() + bulletListHolder.getMarker() + " "); + var marker = bulletListHolder.getMarker(); + var spaces = " "; + textContent.write(marker); + textContent.write(spaces); + textContent.pushPrefix(repeat(" ", marker.length() + spaces.length())); } visitChildren(listItem); textContent.block(); + if (!stripNewlines()) { + textContent.popPrefix(); + } } } @@ -268,4 +279,13 @@ private static String stripTrailingNewline(String s) { return s; } } + + // Keep for Android compat (String.repeat only available on Android 12 and later) + private static String repeat(String s, int count) { + var sb = new StringBuilder(s.length() * count); + for (int i = 0; i < count; i++) { + sb.append(s); + } + return sb.toString(); + } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java index 2b9f35070..1fb482785 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java @@ -8,6 +8,7 @@ public class TextContentWriter { private final Appendable buffer; private final LineBreakRendering lineBreakRendering; + private final LinkedList prefixes = new LinkedList<>(); private final LinkedList tight = new LinkedList<>(); private String blockSeparator = null; @@ -36,6 +37,7 @@ public void colon() { public void line() { append('\n'); + writePrefixes(); } public void block() { @@ -61,6 +63,32 @@ public void write(char c) { append(c); } + /** + * Push a prefix onto the top of the stack. All prefixes are written at the beginning of each line, until the + * prefix is popped again. + * + * @param prefix the raw prefix string + */ + public void pushPrefix(String prefix) { + prefixes.addLast(prefix); + } + + /** + * Write a prefix. + * + * @param prefix the raw prefix string to write + */ + public void writePrefix(String prefix) { + write(prefix); + } + + /** + * Remove the last prefix from the top of the stack. + */ + public void popPrefix() { + prefixes.removeLast(); + } + /** * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines @@ -84,12 +112,26 @@ private boolean isTight() { return !tight.isEmpty() && tight.getLast(); } + private void writePrefixes() { + for (String prefix : prefixes) { + append(prefix); + } + } + /** * If a block separator has been enqueued with {@link #block()} but not yet written, write it now. */ private void flushBlockSeparator() { if (blockSeparator != null) { - append(blockSeparator); + if (blockSeparator.equals("\n") || blockSeparator.equals("\n\n")) { + for (int i = 0; i < blockSeparator.length(); i++) { + var sep = blockSeparator.charAt(i); + append(sep); + writePrefixes(); + } + } else { + append(blockSeparator); + } blockSeparator = null; } } diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index bc443e0e2..46757e0c3 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -116,14 +116,14 @@ public void textContentLists() { assertSeparate(s, "bar\n\n1. foo\n 1. bar\n2. foo"); assertStripped(s, "bar 1. foo 1. bar 2. foo"); - s = "bar\n* foo\n - bar\n* foo"; - assertCompact(s, "bar\n* foo\n - bar\n* foo"); - assertSeparate(s, "bar\n\n* foo\n - bar\n* foo"); + s = "bar\n* foo\n - bar\n* foo"; + assertCompact(s, "bar\n* foo\n - bar\n* foo"); + assertSeparate(s, "bar\n\n* foo\n - bar\n* foo"); assertStripped(s, "bar foo bar foo"); - s = "bar\n* foo\n 1. bar\n 2. bar\n* foo"; - assertCompact(s, "bar\n* foo\n 1. bar\n 2. bar\n* foo"); - assertSeparate(s, "bar\n\n* foo\n 1. bar\n 2. bar\n* foo"); + s = "bar\n* foo\n 1. bar\n 2. bar\n* foo"; + assertCompact(s, "bar\n* foo\n 1. bar\n 2. bar\n* foo"); + assertSeparate(s, "bar\n\n* foo\n 1. bar\n 2. bar\n* foo"); assertStripped(s, "bar foo 1. bar 2. bar foo"); s = "bar\n1. foo\n * bar\n * bar\n2. foo"; @@ -196,6 +196,17 @@ public void textContentHtml() { assertAll(html, html); } + @Test + public void testContentNestedLists() { + var s = "List:\n" + + "1. 2) 3. \n" + + "end"; + assertCompact(s, s); + + var s2 = "1. A\n 1) B\n 1. Test"; + assertCompact(s2, s2); + } + @Test public void testOverrideNodeRendering() { var nodeRendererFactory = new TextContentNodeRendererFactory() { From 67f8405f4e92086321af75c7bf7718ef2362dc1a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 14 Jan 2026 14:26:28 +0100 Subject: [PATCH 242/345] Make list holder classes innner classes --- .../renderer/text/BulletListHolder.java | 16 ------ .../internal/renderer/text/ListHolder.java | 13 ----- .../renderer/text/OrderedListHolder.java | 26 ---------- .../text/CoreTextContentNodeRenderer.java | 51 +++++++++++++++++-- 4 files changed, 48 insertions(+), 58 deletions(-) delete mode 100644 commonmark/src/main/java/org/commonmark/internal/renderer/text/BulletListHolder.java delete mode 100644 commonmark/src/main/java/org/commonmark/internal/renderer/text/ListHolder.java delete mode 100644 commonmark/src/main/java/org/commonmark/internal/renderer/text/OrderedListHolder.java diff --git a/commonmark/src/main/java/org/commonmark/internal/renderer/text/BulletListHolder.java b/commonmark/src/main/java/org/commonmark/internal/renderer/text/BulletListHolder.java deleted file mode 100644 index a9271dcdb..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/renderer/text/BulletListHolder.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.commonmark.internal.renderer.text; - -import org.commonmark.node.BulletList; - -public class BulletListHolder extends ListHolder { - private final String marker; - - public BulletListHolder(ListHolder parent, BulletList list) { - super(parent); - marker = list.getMarker(); - } - - public String getMarker() { - return marker; - } -} diff --git a/commonmark/src/main/java/org/commonmark/internal/renderer/text/ListHolder.java b/commonmark/src/main/java/org/commonmark/internal/renderer/text/ListHolder.java deleted file mode 100644 index 14ca5b9af..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/renderer/text/ListHolder.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.commonmark.internal.renderer.text; - -public abstract class ListHolder { - private final ListHolder parent; - - ListHolder(ListHolder parent) { - this.parent = parent; - } - - public ListHolder getParent() { - return parent; - } -} diff --git a/commonmark/src/main/java/org/commonmark/internal/renderer/text/OrderedListHolder.java b/commonmark/src/main/java/org/commonmark/internal/renderer/text/OrderedListHolder.java deleted file mode 100644 index e5e470951..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/renderer/text/OrderedListHolder.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.commonmark.internal.renderer.text; - -import org.commonmark.node.OrderedList; - -public class OrderedListHolder extends ListHolder { - private final String delimiter; - private int counter; - - public OrderedListHolder(ListHolder parent, OrderedList list) { - super(parent); - delimiter = list.getMarkerDelimiter() != null ? list.getMarkerDelimiter() : "."; - counter = list.getMarkerStartNumber() != null ? list.getMarkerStartNumber() : 1; - } - - public String getDelimiter() { - return delimiter; - } - - public int getCounter() { - return counter; - } - - public void increaseCounter() { - counter++; - } -} diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java index 2a27571c6..ee564cbdb 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -1,8 +1,5 @@ package org.commonmark.renderer.text; -import org.commonmark.internal.renderer.text.BulletListHolder; -import org.commonmark.internal.renderer.text.ListHolder; -import org.commonmark.internal.renderer.text.OrderedListHolder; import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; @@ -288,4 +285,52 @@ private static String repeat(String s, int count) { } return sb.toString(); } + + private static class BulletListHolder extends ListHolder { + private final String marker; + + public BulletListHolder(ListHolder parent, BulletList list) { + super(parent); + marker = list.getMarker(); + } + + public String getMarker() { + return marker; + } + } + + private abstract static class ListHolder { + private final ListHolder parent; + + ListHolder(ListHolder parent) { + this.parent = parent; + } + + public ListHolder getParent() { + return parent; + } + } + + private static class OrderedListHolder extends ListHolder { + private final String delimiter; + private int counter; + + public OrderedListHolder(ListHolder parent, OrderedList list) { + super(parent); + delimiter = list.getMarkerDelimiter() != null ? list.getMarkerDelimiter() : "."; + counter = list.getMarkerStartNumber() != null ? list.getMarkerStartNumber() : 1; + } + + public String getDelimiter() { + return delimiter; + } + + public int getCounter() { + return counter; + } + + public void increaseCounter() { + counter++; + } + } } From c946bfe1ac61697a6085315de45c96e2f22b2893 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 14 Jan 2026 14:42:14 +0100 Subject: [PATCH 243/345] Make nested brackets optimization more effective again --- .../java/org/commonmark/internal/InlineParserImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index fef30c0f7..44422f421 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -383,14 +383,13 @@ private LinkInfo parseLinkInfo(Bracket opener, Position beforeClose) { // - Collapsed: `[foo][]` (foo is both the text and label) // - Shortcut: `[foo]` (foo is both the text and label) - String text = scanner.getSource(opener.contentPosition, beforeClose).getContent(); - // Starting position is after the closing `]` - Position afterClose = scanner.position(); + var afterClose = scanner.position(); // Maybe an inline link/image var destinationTitle = parseInlineDestinationTitle(scanner); if (destinationTitle != null) { + var text = scanner.getSource(opener.contentPosition, beforeClose).getContent(); return new LinkInfoImpl(opener.markerNode, opener.bracketNode, text, null, destinationTitle.destination, destinationTitle.title, afterClose); } // Not an inline link/image, rewind back to after `]`. @@ -401,7 +400,7 @@ private LinkInfo parseLinkInfo(Bracket opener, Position beforeClose) { // failed to be parsed as an inline link/image before. // See if there's a link label like `[bar]` or `[]` - String label = parseLinkLabel(scanner); + var label = parseLinkLabel(scanner); if (label == null) { // No label, rewind back scanner.setPosition(afterClose); @@ -413,6 +412,7 @@ private LinkInfo parseLinkInfo(Bracket opener, Position beforeClose) { return null; } + var text = scanner.getSource(opener.contentPosition, beforeClose).getContent(); return new LinkInfoImpl(opener.markerNode, opener.bracketNode, text, label, null, null, afterClose); } From eb7bac0c12a65c77fb5988cb36e3370fc2b05def Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 14 Jan 2026 14:48:54 +0100 Subject: [PATCH 244/345] Prepare CHANGELOG for version 0.27.1 --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d091306ae..b973b700f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. -## [Unreleased] +## [0.27.1] - 2026-01-14 ### Fixed - Line(s) after a hard line break would sometimes also get an unwanted hard line break, e.g. if they ended in emphasis or other non-text inlines (#415) - `TextContentRenderer` (for plain text): Fix nested lists on the same line (#413) +- Fix minor performance regression with pathological input (deeply nested + brackets) that was introduced in version 0.23.0. ## [0.27.0] - 2025-10-12 ### Added @@ -516,7 +518,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. -[Unreleased]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.0...main +[0.27.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.0...commonmark-parent-0.27.1 [0.27.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.26.0...commonmark-parent-0.27.0 [0.26.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.1...commonmark-parent-0.26.0 [0.25.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.0...commonmark-parent-0.25.1 From cded1b17fd6c557e7bd162b8cbe46e3e21f2adfe Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:55:01 +0000 Subject: [PATCH 245/345] [maven-release-plugin] prepare release commonmark-parent-0.27.1 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 26 ++++++++++++------------ 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 5b6989097..9bc18a017 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1-SNAPSHOT + 0.27.1 commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 09d962e41..1f3ac5bed 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1-SNAPSHOT + 0.27.1 commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 77bea3f36..1f82a3c60 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1-SNAPSHOT + 0.27.1 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 00dd10420..575e83046 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1-SNAPSHOT + 0.27.1 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index f3ede6535..3bc414ba8 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1-SNAPSHOT + 0.27.1 commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 384bd785f..347fef760 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1-SNAPSHOT + 0.27.1 commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index d9789dfe6..a3e95a71d 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1-SNAPSHOT + 0.27.1 commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index bbedcf976..2afe91d63 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1-SNAPSHOT + 0.27.1 commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index b4aea9395..4d202c514 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.27.1-SNAPSHOT + 0.27.1 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 0f9f3b73b..52e62c511 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1-SNAPSHOT + 0.27.1 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 016bd600b..63a1a504b 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1-SNAPSHOT + 0.27.1 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index e5983e81a..2aeafff1f 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1-SNAPSHOT + 0.27.1 commonmark diff --git a/pom.xml b/pom.xml index 7818b0ba4..fea84fd5d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.27.1-SNAPSHOT + 0.27.1 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,57 +132,57 @@ org.commonmark commonmark - 0.27.1-SNAPSHOT + 0.27.1 org.commonmark commonmark-ext-autolink - 0.27.1-SNAPSHOT + 0.27.1 org.commonmark commonmark-ext-footnotes - 0.27.1-SNAPSHOT + 0.27.1 org.commonmark commonmark-ext-image-attributes - 0.27.1-SNAPSHOT + 0.27.1 org.commonmark commonmark-ext-ins - 0.27.1-SNAPSHOT + 0.27.1 org.commonmark commonmark-ext-gfm-strikethrough - 0.27.1-SNAPSHOT + 0.27.1 org.commonmark commonmark-ext-gfm-tables - 0.27.1-SNAPSHOT + 0.27.1 org.commonmark commonmark-ext-heading-anchor - 0.27.1-SNAPSHOT + 0.27.1 org.commonmark commonmark-ext-task-list-items - 0.27.1-SNAPSHOT + 0.27.1 org.commonmark commonmark-ext-yaml-front-matter - 0.27.1-SNAPSHOT + 0.27.1 org.commonmark commonmark-test-util - 0.27.1-SNAPSHOT + 0.27.1 @@ -309,7 +309,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - HEAD + commonmark-parent-0.27.1 From 502da7839116625c3ed5454decd20b5b084388d6 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:55:02 +0000 Subject: [PATCH 246/345] [maven-release-plugin] prepare for next development iteration --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 26 ++++++++++++------------ 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 9bc18a017..a99fd0b8c 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1 + 0.27.2-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 1f3ac5bed..8bb88d74a 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1 + 0.27.2-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 1f82a3c60..f6cedc69a 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1 + 0.27.2-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 575e83046..4e94f623c 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1 + 0.27.2-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 3bc414ba8..707948a10 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1 + 0.27.2-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 347fef760..456ac04a2 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1 + 0.27.2-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index a3e95a71d..a06c27b29 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1 + 0.27.2-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 2afe91d63..0fc164672 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1 + 0.27.2-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 4d202c514..860cfbac7 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.27.1 + 0.27.2-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 52e62c511..3433b42d4 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1 + 0.27.2-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 63a1a504b..699adb3de 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1 + 0.27.2-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 2aeafff1f..76dad6c83 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.1 + 0.27.2-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index fea84fd5d..b3a8e2b9f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.27.1 + 0.27.2-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -132,57 +132,57 @@ org.commonmark commonmark - 0.27.1 + 0.27.2-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.27.1 + 0.27.2-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.27.1 + 0.27.2-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.27.1 + 0.27.2-SNAPSHOT org.commonmark commonmark-ext-ins - 0.27.1 + 0.27.2-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.27.1 + 0.27.2-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.27.1 + 0.27.2-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.27.1 + 0.27.2-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.27.1 + 0.27.2-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.27.1 + 0.27.2-SNAPSHOT org.commonmark commonmark-test-util - 0.27.1 + 0.27.2-SNAPSHOT @@ -309,7 +309,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - commonmark-parent-0.27.1 + HEAD From 23f3db80c53bbb9b7d67ab7748350c5bdf2364d6 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 14 Jan 2026 15:04:28 +0100 Subject: [PATCH 247/345] README: Bump version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 702cc3abc..a917167f6 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.27.0 + 0.27.1 ``` @@ -291,7 +291,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.27.0 + 0.27.1 ``` From af45936db125ed9e29b2229551c41425e8bd8162 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 21:55:37 +0000 Subject: [PATCH 248/345] Bump org.assertj:assertj-core from 3.27.3 to 3.27.7 Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.3 to 3.27.7. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.3...assertj-build-3.27.7) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-version: 3.27.7 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b3a8e2b9f..d55d1fd85 100644 --- a/pom.xml +++ b/pom.xml @@ -194,7 +194,7 @@ org.assertj assertj-core - 3.27.3 + 3.27.7 org.openjdk.jmh From 057f13dd8e9472a45c714ad0c06a1a425ec21f61 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Tue, 3 Mar 2026 17:02:16 +0100 Subject: [PATCH 249/345] Add GFM alerts extension Co-Authored-By: Claude Opus 4.6 --- commonmark-ext-gfm-alerts/README.md | 70 +++ commonmark-ext-gfm-alerts/pom.xml | 27 + .../src/main/java/module-info.java | 5 + .../org/commonmark/ext/gfm/alerts/Alert.java | 19 + .../ext/gfm/alerts/AlertsExtension.java | 117 +++++ .../internal/AlertHtmlNodeRenderer.java | 77 +++ .../internal/AlertMarkdownNodeRenderer.java | 38 ++ .../alerts/internal/AlertNodeRenderer.java | 22 + .../alerts/internal/AlertPostProcessor.java | 170 ++++++ .../src/main/javadoc/overview.html | 6 + .../alerts/AlertsMarkdownRendererTest.java | 73 +++ .../ext/gfm/alerts/AlertsSpecTest.java | 43 ++ .../commonmark/ext/gfm/alerts/AlertsTest.java | 140 +++++ .../gfm/alerts/examples/AlertsExample.java | 94 ++++ .../src/test/resources/alerts-spec.txt | 492 ++++++++++++++++++ pom.xml | 1 + 16 files changed, 1394 insertions(+) create mode 100644 commonmark-ext-gfm-alerts/README.md create mode 100644 commonmark-ext-gfm-alerts/pom.xml create mode 100644 commonmark-ext-gfm-alerts/src/main/java/module-info.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java create mode 100644 commonmark-ext-gfm-alerts/src/main/javadoc/overview.html create mode 100644 commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java create mode 100644 commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java create mode 100644 commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java create mode 100644 commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java create mode 100644 commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt diff --git a/commonmark-ext-gfm-alerts/README.md b/commonmark-ext-gfm-alerts/README.md new file mode 100644 index 000000000..b70584e9b --- /dev/null +++ b/commonmark-ext-gfm-alerts/README.md @@ -0,0 +1,70 @@ +# commonmark-ext-gfm-alerts + +Extension for [commonmark-java](https://github.com/commonmark/commonmark-java) that adds support for [GitHub Flavored Markdown alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts). + +Enables highlighting important information using blockquote syntax with five standard alert types: NOTE, TIP, IMPORTANT, WARNING, and CAUTION. + +## Usage + +#### Markdown Syntax + +```markdown +> [!NOTE] +> Useful information + +> [!WARNING] +> Critical information +``` + +#### Standard GFM Types + +```java +Extension extension = AlertsExtension.create(); +Parser parser = Parser.builder().extensions(List.of(extension)).build(); +HtmlRenderer renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); +``` + +#### Custom Alert Types + +Add custom types beyond the five standard GFM types: + +```java +Extension extension = AlertsExtension.builder() + .addCustomType("INFO", "Information") + .build(); +``` + +Custom types must be UPPERCASE and cannot override standard types. + +#### Styling + +Alerts render as `
    ` elements with CSS classes: + +```html +
    +

    Note

    +

    Content

    +
    +``` + +Basic CSS example: + +```css +.markdown-alert { + padding: 0.5rem 1rem; + margin-bottom: 1rem; + border-left: 4px solid; +} + +.markdown-alert-note { border-color: #0969da; background-color: #ddf4ff; } +.markdown-alert-tip { border-color: #1a7f37; background-color: #dcffe4; } +.markdown-alert-important { border-color: #8250df; background-color: #f6f0ff; } +.markdown-alert-warning { border-color: #9a6700; background-color: #fff8c5; } +.markdown-alert-caution { border-color: #cf222e; background-color: #ffebe9; } +``` + +Icons can be added using CSS `::before` pseudo-elements with GitHub's [Octicons](https://primer.style/octicons/) (info, light-bulb, report, alert, stop icons). + +## License + +See the main commonmark-java project for license information. diff --git a/commonmark-ext-gfm-alerts/pom.xml b/commonmark-ext-gfm-alerts/pom.xml new file mode 100644 index 000000000..5235af6b2 --- /dev/null +++ b/commonmark-ext-gfm-alerts/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + org.commonmark + commonmark-parent + 0.27.2-SNAPSHOT + + + commonmark-ext-gfm-alerts + commonmark-java extension for alerts + commonmark-java extension for GFM alerts (admonition blocks) using [!TYPE] syntax (GitHub Flavored Markdown) + + + + org.commonmark + commonmark + + + + org.commonmark + commonmark-test-util + test + + + + diff --git a/commonmark-ext-gfm-alerts/src/main/java/module-info.java b/commonmark-ext-gfm-alerts/src/main/java/module-info.java new file mode 100644 index 000000000..e8b5aecb7 --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module org.commonmark.ext.gfm.alerts { + exports org.commonmark.ext.gfm.alerts; + + requires transitive org.commonmark; +} diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java new file mode 100644 index 000000000..bb28e7344 --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java @@ -0,0 +1,19 @@ +package org.commonmark.ext.gfm.alerts; + +import org.commonmark.node.CustomBlock; + +/** + * Alert block for highlighting important information using {@code [!TYPE]} syntax. + */ +public class Alert extends CustomBlock { + + private final String type; + + public Alert(String type) { + this.type = type; + } + + public String getType() { + return type; + } +} diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java new file mode 100644 index 000000000..63a69f746 --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -0,0 +1,117 @@ +package org.commonmark.ext.gfm.alerts; + +import org.commonmark.Extension; +import org.commonmark.ext.gfm.alerts.internal.AlertPostProcessor; +import org.commonmark.ext.gfm.alerts.internal.AlertHtmlNodeRenderer; +import org.commonmark.ext.gfm.alerts.internal.AlertMarkdownNodeRenderer; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlNodeRendererFactory; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; +import org.commonmark.renderer.markdown.MarkdownRenderer; + +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * Extension for GFM alerts using {@code [!TYPE]} syntax (GitHub Flavored Markdown). + *

    + * Create with {@link #create()} or {@link #builder()} and configure on builders + * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, + * {@link HtmlRenderer.Builder#extensions(Iterable)}). + * Parsed alerts become {@link Alert} blocks. + */ +public class AlertsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, + MarkdownRenderer.MarkdownRendererExtension { + + static final Set STANDARD_TYPES = Set.of("NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION"); + + private final Map customTypes; + + private AlertsExtension(Builder builder) { + this.customTypes = new LinkedHashMap<>(builder.customTypes); + } + + public static Extension create() { + return builder().build(); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public void extend(Parser.Builder parserBuilder) { + Set allowedTypes = new HashSet<>(STANDARD_TYPES); + allowedTypes.addAll(customTypes.keySet()); + parserBuilder.postProcessor(new AlertPostProcessor(allowedTypes)); + } + + @Override + public void extend(HtmlRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { + @Override + public NodeRenderer create(HtmlNodeRendererContext context) { + return new AlertHtmlNodeRenderer(context, customTypes); + } + }); + } + + @Override + public void extend(MarkdownRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new AlertMarkdownNodeRenderer(context); + } + + @Override + public Set getSpecialCharacters() { + return Set.of(); + } + }); + } + + /** + * Builder for configuring the alerts extension. + */ + public static class Builder { + private final Map customTypes = new LinkedHashMap<>(); + + /** + * Adds a custom alert type with a display title. + *

    + * This can also be used to override the display title of standard GFM types + * (e.g., for localization). + * + * @param type the alert type (must be uppercase) + * @param title the display title for this alert type + * @return {@code this} + */ + public Builder addCustomType(String type, String title) { + if (type == null || type.isEmpty()) { + throw new IllegalArgumentException("Type must not be null or empty"); + } + if (title == null || title.isEmpty()) { + throw new IllegalArgumentException("Title must not be null or empty"); + } + if (!type.equals(type.toUpperCase())) { + throw new IllegalArgumentException("Type must be uppercase: " + type); + } + customTypes.put(type, title); + return this; + } + + /** + * @return a configured {@link Extension} + */ + public Extension build() { + return new AlertsExtension(this); + } + } +} diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java new file mode 100644 index 000000000..202c20290 --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -0,0 +1,77 @@ +package org.commonmark.ext.gfm.alerts.internal; + +import org.commonmark.ext.gfm.alerts.Alert; +import org.commonmark.node.Node; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlWriter; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class AlertHtmlNodeRenderer extends AlertNodeRenderer { + + private final HtmlWriter htmlWriter; + private final HtmlNodeRendererContext context; + private final Map customTypeTitles; + + public AlertHtmlNodeRenderer(HtmlNodeRendererContext context, Map customTypeTitles) { + this.htmlWriter = context.getWriter(); + this.context = context; + this.customTypeTitles = customTypeTitles; + } + + @Override + protected void renderAlert(Alert alert) { + String type = alert.getType(); + String cssClass = type.toLowerCase(); + + htmlWriter.line(); + Map attributes = new LinkedHashMap<>(); + attributes.put("class", "markdown-alert markdown-alert-" + cssClass); + attributes.put("data-alert-type", cssClass); + + htmlWriter.tag("div", context.extendAttributes(alert, "div", attributes)); + htmlWriter.line(); + + // Render alert title + htmlWriter.tag("p", Map.of("class", "markdown-alert-title")); + htmlWriter.text(getAlertTitle(type)); + htmlWriter.tag("/p"); + htmlWriter.line(); + + // Render children (the alert content) + renderChildren(alert); + + htmlWriter.tag("/div"); + htmlWriter.line(); + } + + private String getAlertTitle(String type) { + if (customTypeTitles.containsKey(type)) { + return customTypeTitles.get(type); + } + switch (type) { + case "NOTE": + return "Note"; + case "TIP": + return "Tip"; + case "IMPORTANT": + return "Important"; + case "WARNING": + return "Warning"; + case "CAUTION": + return "Caution"; + default: + throw new IllegalStateException("Unknown alert type: " + type); + } + } + + private void renderChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } +} diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java new file mode 100644 index 000000000..edc988c94 --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java @@ -0,0 +1,38 @@ +package org.commonmark.ext.gfm.alerts.internal; + +import org.commonmark.ext.gfm.alerts.Alert; +import org.commonmark.node.Node; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownWriter; + +public class AlertMarkdownNodeRenderer extends AlertNodeRenderer { + + private final MarkdownWriter writer; + private final MarkdownNodeRendererContext context; + + public AlertMarkdownNodeRenderer(MarkdownNodeRendererContext context) { + this.writer = context.getWriter(); + this.context = context; + } + + @Override + protected void renderAlert(Alert alert) { + // First line: > [!TYPE] + writer.writePrefix("> "); + writer.pushPrefix("> "); + writer.raw("[!" + alert.getType() + "]"); + writer.line(); + renderChildren(alert); + writer.popPrefix(); + writer.block(); + } + + private void renderChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } +} diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java new file mode 100644 index 000000000..dcb1a25eb --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java @@ -0,0 +1,22 @@ +package org.commonmark.ext.gfm.alerts.internal; + +import org.commonmark.ext.gfm.alerts.Alert; +import org.commonmark.renderer.NodeRenderer; + +import java.util.Set; + +public abstract class AlertNodeRenderer implements NodeRenderer { + + @Override + public Set> getNodeTypes() { + return Set.of(Alert.class); + } + + @Override + public void render(org.commonmark.node.Node node) { + Alert alert = (Alert) node; + renderAlert(alert); + } + + protected abstract void renderAlert(Alert alert); +} diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java new file mode 100644 index 000000000..0d02c3527 --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java @@ -0,0 +1,170 @@ +package org.commonmark.ext.gfm.alerts.internal; + +import org.commonmark.ext.gfm.alerts.Alert; +import org.commonmark.node.*; +import org.commonmark.parser.PostProcessor; + +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class AlertPostProcessor implements PostProcessor { + + // Case-insensitive matching for alert type (GitHub supports any case) + private static final Pattern ALERT_PATTERN = Pattern.compile("^\\[!([a-zA-Z]+)]\\s*$"); + + private final Set allowedTypes; + + public AlertPostProcessor(Set allowedTypes) { + this.allowedTypes = allowedTypes; + } + + @Override + public Node process(Node document) { + AlertVisitor visitor = new AlertVisitor(allowedTypes); + document.accept(visitor); + return document; + } + + private static class AlertVisitor extends AbstractVisitor { + + private final Set allowedTypes; + + AlertVisitor(Set allowedTypes) { + this.allowedTypes = allowedTypes; + } + + @Override + public void visit(BlockQuote blockQuote) { + // Only convert top-level block quotes (direct children of Document). + // This matches GitHub's behavior where alerts are only detected at the document level. + if (blockQuote.getParent() instanceof Document) { + if (tryConvertToAlert(blockQuote)) { + return; + } + } + visitChildren(blockQuote); + } + + private boolean tryConvertToAlert(BlockQuote blockQuote) { + Node firstChild = blockQuote.getFirstChild(); + if (!(firstChild instanceof Paragraph)) { + return false; + } + + Paragraph paragraph = (Paragraph) firstChild; + Node firstInline = paragraph.getFirstChild(); + if (!(firstInline instanceof Text)) { + return false; + } + + Text textNode = (Text) firstInline; + String literal = textNode.getLiteral(); + + // The alert marker can be the entire text node content, or just the first line + // before a line break. We need to check both cases. + // Trailing spaces on the marker line create a HardLineBreak instead of SoftLineBreak. + String markerText; + Node afterMarker = firstInline.getNext(); + if (afterMarker instanceof SoftLineBreak || afterMarker instanceof HardLineBreak || afterMarker == null) { + markerText = literal; + } else { + // Text followed by something other than a line break on same line - not an alert + return false; + } + + Matcher matcher = ALERT_PATTERN.matcher(markerText); + if (!matcher.matches()) { + return false; + } + + String type = matcher.group(1).toUpperCase(); + if (!allowedTypes.contains(type)) { + return false; + } + + // Must have content after the marker line. An alert with ONLY the marker + // and no content is a normal blockquote on GitHub. + boolean hasContentAfterMarker; + if (afterMarker instanceof SoftLineBreak || afterMarker instanceof HardLineBreak) { + // There's a line break after marker - check if there's content after it + hasContentAfterMarker = afterMarker.getNext() != null || paragraph.getNext() != null; + } else { + // Marker is the only thing in this text node + hasContentAfterMarker = paragraph.getNext() != null; + } + + if (!hasContentAfterMarker) { + return false; + } + + // Check if the content after the marker is only whitespace + if (isContentWhitespaceOnly(paragraph, firstInline)) { + return false; + } + + // Valid alert. Create Alert node and transfer children. + Alert alert = new Alert(type); + + blockQuote.insertAfter(alert); + + // Remove the marker text and line break from the first paragraph + if (afterMarker instanceof SoftLineBreak || afterMarker instanceof HardLineBreak) { + afterMarker.unlink(); + } + firstInline.unlink(); + + // If paragraph is now empty, remove it + if (paragraph.getFirstChild() == null) { + paragraph.unlink(); + } + + // Move remaining children from blockquote to alert + Node child = blockQuote.getFirstChild(); + while (child != null) { + Node next = child.getNext(); + alert.appendChild(child); + child = next; + } + + blockQuote.unlink(); + return true; + } + + private boolean isContentWhitespaceOnly(Paragraph firstParagraph, Node markerNode) { + // Check inline nodes after the marker in the first paragraph + Node next = markerNode.getNext(); + while (next != null) { + if (next instanceof Text) { + if (!((Text) next).getLiteral().trim().isEmpty()) { + return false; + } + } else if (!(next instanceof SoftLineBreak) && !(next instanceof HardLineBreak)) { + return false; + } + next = next.getNext(); + } + + // Check block-level siblings after the first paragraph + Node block = firstParagraph.getNext(); + while (block != null) { + if (block instanceof Paragraph) { + Node child = block.getFirstChild(); + while (child != null) { + if (child instanceof Text && !((Text) child).getLiteral().trim().isEmpty()) { + return false; + } else if (!(child instanceof Text) && !(child instanceof SoftLineBreak) && !(child instanceof HardLineBreak)) { + return false; + } + child = child.getNext(); + } + } else { + return false; + } + block = block.getNext(); + } + + return true; + } + } +} diff --git a/commonmark-ext-gfm-alerts/src/main/javadoc/overview.html b/commonmark-ext-gfm-alerts/src/main/javadoc/overview.html new file mode 100644 index 000000000..145232a87 --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/main/javadoc/overview.html @@ -0,0 +1,6 @@ + + +Extension for GitHub Flavored Markdown (GFM) alerts using blockquote syntax +

    See {@link org.commonmark.ext.gfm.alerts.AlertsExtension}

    + + diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java new file mode 100644 index 000000000..aca90e2df --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java @@ -0,0 +1,73 @@ +package org.commonmark.ext.gfm.alerts; + +import org.commonmark.Extension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.markdown.MarkdownRenderer; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AlertsMarkdownRendererTest { + + private static final Set EXTENSIONS = Set.of(AlertsExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void alertRoundTrip() { + assertRoundTrip("> [!WARNING]\n> Be careful\n"); + } + + @Test + public void allStandardTypesRoundTrip() { + assertRoundTrip("> [!NOTE]\n> Note\n"); + assertRoundTrip("> [!TIP]\n> Tip\n"); + assertRoundTrip("> [!IMPORTANT]\n> Important\n"); + assertRoundTrip("> [!WARNING]\n> Warning\n"); + assertRoundTrip("> [!CAUTION]\n> Caution\n"); + } + + @Test + public void lowercaseTypeRendersAsUppercase() { + // Lowercase input gets normalized to uppercase type + String rendered = RENDERER.render(PARSER.parse("> [!note]\n> Content\n")); + assertThat(rendered).isEqualTo("> [!NOTE]\n> Content\n"); + } + + @Test + public void alertWithMultipleParagraphs() { + String input = "> [!NOTE]\n> First paragraph\n>\n> Second paragraph\n"; + // MarkdownWriter always writes the prefix including trailing space + String expected = "> [!NOTE]\n> First paragraph\n> \n> Second paragraph\n"; + String rendered = RENDERER.render(PARSER.parse(input)); + assertThat(rendered).isEqualTo(expected); + } + + @Test + public void customTypeRoundTrip() { + Extension extension = AlertsExtension.builder() + .addCustomType("INFO", "Information") + .build(); + + Parser parser = Parser.builder().extensions(Set.of(extension)).build(); + MarkdownRenderer renderer = MarkdownRenderer.builder().extensions(Set.of(extension)).build(); + + String input = "> [!INFO]\n> Custom type\n"; + String rendered = renderer.render(parser.parse(input)); + assertThat(rendered).isEqualTo(input); + } + + @Test + public void alertWithList() { + String input = "> [!NOTE]\n> Items:\n> \n> - First\n> - Second\n"; + String rendered = RENDERER.render(PARSER.parse(input)); + assertThat(rendered).isEqualTo(input); + } + + private void assertRoundTrip(String input) { + String rendered = RENDERER.render(PARSER.parse(input)); + assertThat(rendered).isEqualTo(input); + } +} diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java new file mode 100644 index 000000000..fc77674df --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java @@ -0,0 +1,43 @@ +package org.commonmark.ext.gfm.alerts; + +import org.commonmark.Extension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; +import org.commonmark.testutil.example.Example; +import org.commonmark.testutil.example.ExampleReader; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; + +import java.net.URL; +import java.util.List; +import java.util.Set; + +@ParameterizedClass +@MethodSource("data") +public class AlertsSpecTest extends RenderingTestCase { + + private static final Set EXTENSIONS = Set.of(AlertsExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + + @Parameter + Example example; + + static List data() { + URL spec = AlertsSpecTest.class.getResource("/alerts-spec.txt"); + return ExampleReader.readExamples(spec, "alert"); + } + + @Test + public void testHtmlRendering() { + assertRendering(example.getSource(), example.getHtml()); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} \ No newline at end of file diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java new file mode 100644 index 000000000..c46c532fe --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -0,0 +1,140 @@ +package org.commonmark.ext.gfm.alerts; + +import org.commonmark.Extension; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class AlertsTest { + + private static final Set EXTENSIONS = Set.of(AlertsExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + + // Custom types + + @Test + public void customType() { + Extension extension = AlertsExtension.builder() + .addCustomType("INFO", "Information") + .build(); + + Parser parser = Parser.builder().extensions(Set.of(extension)).build(); + HtmlRenderer renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); + + assertThat(renderer.render(parser.parse("> [!INFO]\n> Custom alert"))).isEqualTo( + "
    \n" + + "

    Information

    \n" + + "

    Custom alert

    \n" + + "
    \n"); + } + + @Test + public void multipleCustomTypes() { + Extension extension = AlertsExtension.builder() + .addCustomType("INFO", "Information") + .addCustomType("SUCCESS", "Success!") + .addCustomType("DANGER", "Danger!") + .build(); + + Parser parser = Parser.builder().extensions(Set.of(extension)).build(); + HtmlRenderer renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); + + assertThat(renderer.render(parser.parse("> [!INFO]\n> Info content\n\n> [!SUCCESS]\n> Success content\n\n> [!DANGER]\n> Danger content"))).isEqualTo( + "
    \n" + + "

    Information

    \n" + + "

    Info content

    \n" + + "
    \n" + + "
    \n" + + "

    Success!

    \n" + + "

    Success content

    \n" + + "
    \n" + + "
    \n" + + "

    Danger!

    \n" + + "

    Danger content

    \n" + + "
    \n"); + } + + @Test + public void standardTypesWithCustomConfigured() { + Extension extension = AlertsExtension.builder() + .addCustomType("INFO", "Information") + .build(); + + Parser parser = Parser.builder().extensions(Set.of(extension)).build(); + HtmlRenderer renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); + + assertThat(renderer.render(parser.parse("> [!NOTE]\n> Standard type"))).isEqualTo( + "
    \n" + + "

    Note

    \n" + + "

    Standard type

    \n" + + "
    \n"); + } + + @Test + public void overrideStandardTypeTitle() { + Extension extension = AlertsExtension.builder() + .addCustomType("NOTE", "Nota") + .build(); + + Parser parser = Parser.builder().extensions(Set.of(extension)).build(); + HtmlRenderer renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); + + assertThat(renderer.render(parser.parse("> [!NOTE]\n> Localized title"))).isEqualTo( + "
    \n" + + "

    Nota

    \n" + + "

    Localized title

    \n" + + "
    \n"); + } + + // Custom type validation + + @Test + public void customTypeMustBeUppercase() { + assertThrows(IllegalArgumentException.class, () -> + AlertsExtension.builder().addCustomType("info", "Information").build()); + } + + @Test + public void customTypeMustNotBeEmpty() { + assertThrows(IllegalArgumentException.class, () -> + AlertsExtension.builder().addCustomType("", "Title").build()); + } + + @Test + public void customTypeTitleMustNotBeEmpty() { + assertThrows(IllegalArgumentException.class, () -> + AlertsExtension.builder().addCustomType("INFO", "").build()); + } + + // AST + + @Test + public void alertParsedAsAlertNode() { + Node document = PARSER.parse("> [!NOTE]\n> This is a note"); + Node firstChild = document.getFirstChild(); + assertThat(firstChild).isInstanceOf(Alert.class); + Alert alert = (Alert) firstChild; + assertThat(alert.getType()).isEqualTo("NOTE"); + } + + @Test + public void customTypeParsedAsAlertNode() { + Extension extension = AlertsExtension.builder() + .addCustomType("INFO", "Information") + .build(); + + Parser parser = Parser.builder().extensions(Set.of(extension)).build(); + + Node document = parser.parse("> [!INFO]\n> Custom alert"); + Alert alert = (Alert) document.getFirstChild(); + + assertThat(alert.getType()).isEqualTo("INFO"); + } + +} \ No newline at end of file diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java new file mode 100644 index 000000000..dba1628cb --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java @@ -0,0 +1,94 @@ +package org.commonmark.ext.gfm.alerts.examples; + +import org.commonmark.Extension; +import org.commonmark.ext.gfm.alerts.AlertsExtension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; + +import java.util.List; + +/** + * Example demonstrating the use of the GFM Alerts extension. + */ +public class AlertsExample { + + public static void main(String[] args) { + standardTypesExample(); + System.out.println("\n" + "=".repeat(60) + "\n"); + customTypesExample(); + } + + private static void standardTypesExample() { + System.out.println("STANDARD GFM ALERT TYPES"); + System.out.println("=".repeat(60)); + + // Create the alerts extension with default settings + Extension extension = AlertsExtension.create(); + + Parser parser = Parser.builder() + .extensions(List.of(extension)) + .build(); + + HtmlRenderer renderer = HtmlRenderer.builder() + .extensions(List.of(extension)) + .build(); + + // Example markdown with all standard alert types + String markdown = "# GFM Alerts Demo\n\n" + + "> [!NOTE]\n" + + "> Highlights information that users should take into account.\n\n" + + "> [!TIP]\n" + + "> Helpful advice for doing things better.\n\n" + + "> [!IMPORTANT]\n" + + "> Key information users need to know.\n\n" + + "> [!WARNING]\n" + + "> Urgent info that needs immediate attention.\n\n" + + "> [!CAUTION]\n" + + "> Advises about risks or negative outcomes.\n"; + + String html = renderer.render(parser.parse(markdown)); + + System.out.println("Markdown Input:"); + System.out.println(markdown); + System.out.println("\nHTML Output:"); + System.out.println(html); + } + + private static void customTypesExample() { + System.out.println("CUSTOM ALERT TYPES"); + System.out.println("=".repeat(60)); + + // Create extension with custom types + Extension extension = AlertsExtension.builder() + .addCustomType("INFO", "Information") + .addCustomType("SUCCESS", "Success") + .addCustomType("DANGER", "Danger") + .build(); + + Parser parser = Parser.builder() + .extensions(List.of(extension)) + .build(); + + HtmlRenderer renderer = HtmlRenderer.builder() + .extensions(List.of(extension)) + .build(); + + // Example markdown with custom alert types + String markdown = "# Custom Alert Types\n\n" + + "> [!INFO]\n" + + "> This is a custom information alert.\n\n" + + "> [!SUCCESS]\n" + + "> Operation completed successfully!\n\n" + + "> [!DANGER]\n" + + "> This action is dangerous and irreversible.\n\n" + + "> [!NOTE]\n" + + "> Standard types still work alongside custom types.\n"; + + String html = renderer.render(parser.parse(markdown)); + + System.out.println("Markdown Input:"); + System.out.println(markdown); + System.out.println("\nHTML Output:"); + System.out.println(html); + } +} diff --git a/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt b/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt new file mode 100644 index 000000000..ef2809336 --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt @@ -0,0 +1,492 @@ +# Alerts + +Expectations verified against GitHub Markdown API (gh api markdown -f mode=gfm). +Our HTML omits GitHub's SVG icons and uses a `data-alert-type` attribute instead. + +## Standard types + +```````````````````````````````` example alert +> [!NOTE] +> This is a note +. +
    +

    Note

    +

    This is a note

    +
    +```````````````````````````````` + +```````````````````````````````` example alert +> [!TIP] +> This is a tip +. +
    +

    Tip

    +

    This is a tip

    +
    +```````````````````````````````` + +```````````````````````````````` example alert +> [!IMPORTANT] +> This is important +. +
    +

    Important

    +

    This is important

    +
    +```````````````````````````````` + +```````````````````````````````` example alert +> [!WARNING] +> This is a warning +. +
    +

    Warning

    +

    This is a warning

    +
    +```````````````````````````````` + +```````````````````````````````` example alert +> [!CAUTION] +> This is a caution +. +
    +

    Caution

    +

    This is a caution

    +
    +```````````````````````````````` + +## Case insensitivity + +Alert type matching is case-insensitive. + +```````````````````````````````` example alert +> [!note] +> Content +. +
    +

    Note

    +

    Content

    +
    +```````````````````````````````` + +```````````````````````````````` example alert +> [!Note] +> Content +. +
    +

    Note

    +

    Content

    +
    +```````````````````````````````` + +## Alert content + +Marker alone in first paragraph, blank line, then content: + +```````````````````````````````` example alert +> [!NOTE] +> +> Content +. +
    +

    Note

    +

    Content

    +
    +```````````````````````````````` + +Multiple paragraphs: + +```````````````````````````````` example alert +> [!NOTE] +> First paragraph +> +> Second paragraph +. +
    +

    Note

    +

    First paragraph

    +

    Second paragraph

    +
    +```````````````````````````````` + +Inline formatting: + +```````````````````````````````` example alert +> [!TIP] +> This is **bold** and *italic* +. +
    +

    Tip

    +

    This is bold and italic

    +
    +```````````````````````````````` + +Code block inside alert: + +```````````````````````````````` example alert +> [!TIP] +> Code: +> +> function() { } +> +> End +. +
    +

    Tip

    +

    Code:

    +
    function() { }
    +
    +

    End

    +
    +```````````````````````````````` + +List inside alert: + +```````````````````````````````` example alert +> [!IMPORTANT] +> Items: +> - First item +> - Second item +. +
    +

    Important

    +

    Items:

    +
      +
    • First item
    • +
    • Second item
    • +
    +
    +```````````````````````````````` + +Links inside alert: + +```````````````````````````````` example alert +> [!NOTE] +> Check out [this link](https://example.com) for more info +. +
    +

    Note

    +

    Check out this link for more info

    +
    +```````````````````````````````` + +Heading inside alert: + +```````````````````````````````` example alert +> [!IMPORTANT] +> ## Heading +> Content below heading +. +
    +

    Important

    +

    Heading

    +

    Content below heading

    +
    +```````````````````````````````` + +Empty lines in middle of alert: + +```````````````````````````````` example alert +> [!NOTE] +> First +> +> +> After empty lines +. +
    +

    Note

    +

    First

    +

    After empty lines

    +
    +```````````````````````````````` + +## Not an alert + +Text after marker on the same line: + +```````````````````````````````` example alert +> [!NOTE] Some text +. +
    +

    [!NOTE] Some text

    +
    +```````````````````````````````` + +Unknown type: + +```````````````````````````````` example alert +> [!INVALID] +> Some text +. +
    +

    [!INVALID] +Some text

    +
    +```````````````````````````````` + +Unconfigured custom type is not an alert: + +```````````````````````````````` example alert +> [!INFO] +> Should be blockquote +. +
    +

    [!INFO] +Should be blockquote

    +
    +```````````````````````````````` + +Marker with no content: + +```````````````````````````````` example alert +> [!NOTE] +. +
    +

    [!NOTE]

    +
    +```````````````````````````````` + +Whitespace-only content after marker: + +```````````````````````````````` example alert +> [!TIP] +> +> +. +
    +

    [!TIP]

    +
    +```````````````````````````````` + +Extra space inside marker: + +```````````````````````````````` example alert +> [! NOTE] +> Should be blockquote +. +
    +

    [! NOTE] +Should be blockquote

    +
    +```````````````````````````````` + +Missing brackets: + +```````````````````````````````` example alert +> !NOTE +> Should be blockquote +. +
    +

    !NOTE +Should be blockquote

    +
    +```````````````````````````````` + +Missing exclamation mark: + +```````````````````````````````` example alert +> [NOTE] +> Should be blockquote +. +
    +

    [NOTE] +Should be blockquote

    +
    +```````````````````````````````` + +Regular blockquote is not affected: + +```````````````````````````````` example alert +> This is a regular blockquote +. +
    +

    This is a regular blockquote

    +
    +```````````````````````````````` + +## Boundaries + +Trailing spaces after marker: + +```````````````````````````````` example alert +> [!NOTE] +> This is a note +. +
    +

    Note

    +

    This is a note

    +
    +```````````````````````````````` + +Trailing tabs after marker: + +```````````````````````````````` example alert +> [!WARNING]→→ +> Be careful +. +
    +

    Warning

    +

    Be careful

    +
    +```````````````````````````````` + +Leading spaces before blockquote marker: + +```````````````````````````````` example alert + > [!IMPORTANT] + > Content +. +
    +

    Important

    +

    Content

    +
    +```````````````````````````````` + +Blank line after marker ends the blockquote (not an alert): + +```````````````````````````````` example alert +> [!NOTE] + +Some text +. +
    +

    [!NOTE]

    +
    +

    Some text

    +```````````````````````````````` + +Alert followed by blockquote: + +```````````````````````````````` example alert +> [!NOTE] +> This is an alert + +> This is a blockquote +. +
    +

    Note

    +

    This is an alert

    +
    +
    +

    This is a blockquote

    +
    +```````````````````````````````` + +Adjacent alerts: + +```````````````````````````````` example alert +> [!NOTE] +> First alert + +> [!WARNING] +> Second alert +. +
    +

    Note

    +

    First alert

    +
    +
    +

    Warning

    +

    Second alert

    +
    +```````````````````````````````` + +## Nesting and containers + +Nested alert inside alert renders as blockquote: + +```````````````````````````````` example alert +> [!NOTE] +> This is a note +>> [!WARNING] +>> Nested content +. +
    +

    Note

    +

    This is a note

    +
    +

    [!WARNING] +Nested content

    +
    +
    +```````````````````````````````` + +Nested blockquote inside alert: + +```````````````````````````````` example alert +> [!NOTE] +> This is a note +>> Nested blockquote +. +
    +

    Note

    +

    This is a note

    +
    +

    Nested blockquote

    +
    +
    +```````````````````````````````` + +Alert inside list item stays as blockquote: + +```````````````````````````````` example alert +- > [!NOTE] + > Test +. +
      +
    • +
      +

      [!NOTE] +Test

      +
      +
    • +
    +```````````````````````````````` + +Alert marker in content is treated as text: + +```````````````````````````````` example alert +> [!NOTE] +> This is a note +> [!WARNING] +> This is still part of the note +. +
    +

    Note

    +

    This is a note +[!WARNING] +This is still part of the note

    +
    +```````````````````````````````` + +## Continuation and interruption + +Lazy continuation: + +```````````````````````````````` example alert +> [!NOTE] +> First line +Lazy continuation +> Continues alert +. +
    +

    Note

    +

    First line +Lazy continuation +Continues alert

    +
    +```````````````````````````````` + +Alert type after regular blockquote content is not an alert: + +```````````````````````````````` example alert +> Regular blockquote +> [!NOTE] +> More text +. +
    +

    Regular blockquote +[!NOTE] +More text

    +
    +```````````````````````````````` \ No newline at end of file diff --git a/pom.xml b/pom.xml index d55d1fd85..58c3ead2c 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ commonmark commonmark-ext-autolink commonmark-ext-footnotes + commonmark-ext-gfm-alerts commonmark-ext-gfm-strikethrough commonmark-ext-gfm-tables commonmark-ext-heading-anchor From 5815c3c0b17f52b53e65465fd8ff65da8e6304fa Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Fri, 27 Mar 2026 17:39:20 +0100 Subject: [PATCH 250/345] Add spec generator script and match GitHub softbreak rendering Add a jbang script (generate-alerts-spec.java) that generates alerts-spec.txt from a template (alerts-spec-template.md) by rendering each example through the GitHub Markdown API and normalizing the HTML. Configure AlertsSpecTest with softbreak("
    ") to match GitHub's rendering, reducing the normalizations needed in the generator. Co-Authored-By: Claude Opus 4.6 --- .../ext/gfm/alerts/AlertsSpecTest.java | 3 +- .../test/resources/alerts-spec-template.md | 280 ++++++++++++++++++ .../src/test/resources/alerts-spec.txt | 28 +- .../test/resources/generate-alerts-spec.java | 111 +++++++ 4 files changed, 407 insertions(+), 15 deletions(-) create mode 100644 commonmark-ext-gfm-alerts/src/test/resources/alerts-spec-template.md create mode 100644 commonmark-ext-gfm-alerts/src/test/resources/generate-alerts-spec.java diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java index fc77674df..8155d8009 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java @@ -21,7 +21,8 @@ public class AlertsSpecTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(AlertsExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + // Use softbreak("
    ") to match GitHub's rendering for easier comparison with GitHub API output. + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).softbreak("
    \n").build(); @Parameter Example example; diff --git a/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec-template.md b/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec-template.md new file mode 100644 index 000000000..9c1cf117b --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec-template.md @@ -0,0 +1,280 @@ +# Alerts + +## Standard types + +```````````````````````````````` example alert +> [!NOTE] +> This is a note +```````````````````````````````` + +```````````````````````````````` example alert +> [!TIP] +> This is a tip +```````````````````````````````` + +```````````````````````````````` example alert +> [!IMPORTANT] +> This is important +```````````````````````````````` + +```````````````````````````````` example alert +> [!WARNING] +> This is a warning +```````````````````````````````` + +```````````````````````````````` example alert +> [!CAUTION] +> This is a caution +```````````````````````````````` + +## Case insensitivity + +Alert type matching is case-insensitive. + +```````````````````````````````` example alert +> [!note] +> Content +```````````````````````````````` + +```````````````````````````````` example alert +> [!Note] +> Content +```````````````````````````````` + +## Alert content + +Marker alone in first paragraph, blank line, then content: + +```````````````````````````````` example alert +> [!NOTE] +> +> Content +```````````````````````````````` + +Multiple paragraphs: + +```````````````````````````````` example alert +> [!NOTE] +> First paragraph +> +> Second paragraph +```````````````````````````````` + +Inline formatting: + +```````````````````````````````` example alert +> [!TIP] +> This is **bold** and *italic* +```````````````````````````````` + +Code block inside alert: + +```````````````````````````````` example alert +> [!TIP] +> Code: +> +> function() { } +> +> End +```````````````````````````````` + +List inside alert: + +```````````````````````````````` example alert +> [!IMPORTANT] +> Items: +> - First item +> - Second item +```````````````````````````````` + +Links inside alert: + +```````````````````````````````` example alert +> [!NOTE] +> Check out [this link](https://example.com) for more info +```````````````````````````````` + +Heading inside alert: + +```````````````````````````````` example alert +> [!IMPORTANT] +> ## Heading +> Content below heading +```````````````````````````````` + +Empty lines in middle of alert: + +```````````````````````````````` example alert +> [!NOTE] +> First +> +> +> After empty lines +```````````````````````````````` + +## Not an alert + +Text after marker on the same line: + +```````````````````````````````` example alert +> [!NOTE] Some text +```````````````````````````````` + +Unknown type: + +```````````````````````````````` example alert +> [!INVALID] +> Some text +```````````````````````````````` + +Unconfigured custom type is not an alert: + +```````````````````````````````` example alert +> [!INFO] +> Should be blockquote +```````````````````````````````` + +Marker with no content: + +```````````````````````````````` example alert +> [!NOTE] +```````````````````````````````` + +Whitespace-only content after marker: + +```````````````````````````````` example alert +> [!TIP] +> +> +```````````````````````````````` + +Extra space inside marker: + +```````````````````````````````` example alert +> [! NOTE] +> Should be blockquote +```````````````````````````````` + +Missing brackets: + +```````````````````````````````` example alert +> !NOTE +> Should be blockquote +```````````````````````````````` + +Missing exclamation mark: + +```````````````````````````````` example alert +> [NOTE] +> Should be blockquote +```````````````````````````````` + +Regular blockquote is not affected: + +```````````````````````````````` example alert +> This is a regular blockquote +```````````````````````````````` + +## Boundaries + +Trailing spaces after marker: + +```````````````````````````````` example alert +> [!NOTE] +> This is a note +```````````````````````````````` + +Trailing tabs after marker: + +```````````````````````````````` example alert +> [!WARNING]→→ +> Be careful +```````````````````````````````` + +Leading spaces before blockquote marker: + +```````````````````````````````` example alert + > [!IMPORTANT] + > Content +```````````````````````````````` + +Blank line after marker ends the blockquote (not an alert): + +```````````````````````````````` example alert +> [!NOTE] + +Some text +```````````````````````````````` + +Alert followed by blockquote: + +```````````````````````````````` example alert +> [!NOTE] +> This is an alert + +> This is a blockquote +```````````````````````````````` + +Adjacent alerts: + +```````````````````````````````` example alert +> [!NOTE] +> First alert + +> [!WARNING] +> Second alert +```````````````````````````````` + +## Nesting and containers + +Nested alert inside alert renders as blockquote: + +```````````````````````````````` example alert +> [!NOTE] +> This is a note +>> [!WARNING] +>> Nested content +```````````````````````````````` + +Nested blockquote inside alert: + +```````````````````````````````` example alert +> [!NOTE] +> This is a note +>> Nested blockquote +```````````````````````````````` + +Alert inside list item stays as blockquote: + +```````````````````````````````` example alert +- > [!NOTE] + > Test +```````````````````````````````` + +Alert marker in content is treated as text: + +```````````````````````````````` example alert +> [!NOTE] +> This is a note +> [!WARNING] +> This is still part of the note +```````````````````````````````` + +## Continuation and interruption + +Lazy continuation: + +```````````````````````````````` example alert +> [!NOTE] +> First line +Lazy continuation +> Continues alert +```````````````````````````````` + +Alert type after regular blockquote content is not an alert: + +```````````````````````````````` example alert +> Regular blockquote +> [!NOTE] +> More text +```````````````````````````````` \ No newline at end of file diff --git a/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt b/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt index ef2809336..6f041fee4 100644 --- a/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt +++ b/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt @@ -219,7 +219,7 @@ Unknown type: > Some text .
    -

    [!INVALID] +

    [!INVALID]
    Some text

    ```````````````````````````````` @@ -231,7 +231,7 @@ Unconfigured custom type is not an alert: > Should be blockquote .
    -

    [!INFO] +

    [!INFO]
    Should be blockquote

    ```````````````````````````````` @@ -265,7 +265,7 @@ Extra space inside marker: > Should be blockquote .
    -

    [! NOTE] +

    [! NOTE]
    Should be blockquote

    ```````````````````````````````` @@ -277,7 +277,7 @@ Missing brackets: > Should be blockquote .
    -

    !NOTE +

    !NOTE
    Should be blockquote

    ```````````````````````````````` @@ -289,7 +289,7 @@ Missing exclamation mark: > Should be blockquote .
    -

    [NOTE] +

    [NOTE]
    Should be blockquote

    ```````````````````````````````` @@ -405,7 +405,7 @@ Nested alert inside alert renders as blockquote:

    Note

    This is a note

    -

    [!WARNING] +

    [!WARNING]
    Nested content

    @@ -436,7 +436,7 @@ Alert inside list item stays as blockquote:
    • -

      [!NOTE] +

      [!NOTE]
      Test

    • @@ -453,8 +453,8 @@ Alert marker in content is treated as text: .

      Note

      -

      This is a note -[!WARNING] +

      This is a note
      +[!WARNING]
      This is still part of the note

      ```````````````````````````````` @@ -471,8 +471,8 @@ Lazy continuation .

      Note

      -

      First line -Lazy continuation +

      First line
      +Lazy continuation
      Continues alert

      ```````````````````````````````` @@ -485,8 +485,8 @@ Alert type after regular blockquote content is not an alert: > More text .
      -

      Regular blockquote -[!NOTE] +

      Regular blockquote
      +[!NOTE]
      More text

      -```````````````````````````````` \ No newline at end of file +```````````````````````````````` diff --git a/commonmark-ext-gfm-alerts/src/test/resources/generate-alerts-spec.java b/commonmark-ext-gfm-alerts/src/test/resources/generate-alerts-spec.java new file mode 100644 index 000000000..06192f107 --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/test/resources/generate-alerts-spec.java @@ -0,0 +1,111 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? + +// Generates alerts-spec.txt from alerts-spec-template.md by rendering each example +// through the GitHub Markdown API and inserting the normalized HTML expectation. +// +// Prerequisites: gh CLI installed and authenticated (gh auth login) +// Usage: cd commonmark-ext-gfm-alerts/src/test/resources && jbang generate-alerts-spec.java + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +class GenerateAlertsSpec { + + private static final String FENCE = "````````````````````````````````"; + private static final String EXAMPLE_OPEN = FENCE + " example alert"; + + public static void main(String[] args) throws Exception { + var templatePath = Path.of("alerts-spec-template.md"); + if (!Files.exists(templatePath)) { + System.err.println("Run from the directory containing alerts-spec-template.md"); + System.exit(1); + } + + var lines = Files.readAllLines(templatePath); + var output = new ArrayList(); + var header = "Expectations verified against GitHub Markdown API (gh api markdown -f mode=gfm).\n" + + "Our HTML omits GitHub's SVG icons and uses a `data-alert-type` attribute instead."; + + int exampleCount = 0; + int i = 0; + while (i < lines.size()) { + var line = lines.get(i); + + // Insert header after the first heading + if (i == 0 && line.startsWith("# ")) { + output.add(line); + output.add(""); + output.add(header); + i++; + continue; + } + + if (line.equals(EXAMPLE_OPEN)) { + // Collect source lines until closing fence + output.add(line); + i++; + var sourceLines = new ArrayList(); + while (i < lines.size() && !lines.get(i).equals(FENCE)) { + sourceLines.add(lines.get(i)); + output.add(lines.get(i)); + i++; + } + + // Render via GitHub API (→ represents tabs in the spec format) + var source = String.join("\n", sourceLines).replace("\u2192", "\t"); + exampleCount++; + System.out.printf("%d: %s%n", exampleCount, + source.substring(0, Math.min(40, source.length())).replace("\n", "\\n")); + + var ghHtml = normalizeHtml(renderViaGh(source)); + + // Insert separator and HTML expectation + output.add("."); + output.add(ghHtml); + output.add(FENCE); + i++; // skip closing fence from template + } else { + output.add(line); + i++; + } + } + + var specPath = Path.of("alerts-spec.txt"); + Files.writeString(specPath, String.join("\n", output) + "\n"); + System.out.println("Done — " + exampleCount + " examples written to alerts-spec.txt"); + } + + static String renderViaGh(String markdown) throws Exception { + var process = new ProcessBuilder("gh", "api", "markdown", "-f", "mode=gfm", "-f", "text=" + markdown) + .redirectErrorStream(true) + .start(); + var output = new String(process.getInputStream().readAllBytes()); + if (process.waitFor() != 0) { + throw new RuntimeException("gh api failed: " + output); + } + return output; + } + + // Normalize GitHub API HTML to match our renderer output. + static String normalizeHtml(String html) { + // Strip GitHub-specific elements and attributes + html = Pattern.compile("]*>.*?", Pattern.DOTALL).matcher(html).replaceAll(""); + html = html.replaceAll(" (dir=\"auto\"|rel=\"nofollow\"|class=\"notranslate\")", ""); + // Add data-alert-type and insert newlines to match our renderer's formatting + html = Pattern.compile("class=\"markdown-alert markdown-alert-(\\w+)\"") + .matcher(html) + .replaceAll("class=\"markdown-alert markdown-alert-$1\" data-alert-type=\"$1\""); + html = Pattern.compile("(data-alert-type=\"\\w+\">)(

      ", "

      \n

      "); + return html.replace("\r\n", "\n").lines() + .map(String::stripTrailing) + .reduce((a, b) -> a + "\n" + b) + .orElse("") + .strip(); + } +} \ No newline at end of file From f3be489803f3446e610f2948fd77d4e821a5cefd Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Mon, 30 Mar 2026 10:55:31 +0200 Subject: [PATCH 251/345] Address review feedback: simplify PostProcessor and clean up code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace AbstractVisitor with direct Document child iteration (only top-level block quotes are alerts, no need to walk the full tree) - Remove unnecessary isContentWhitespaceOnly method (the parser never produces content nodes for whitespace-only block quote continuations) - Remove redundant markerText/literal variables, use textNode.getLiteral() - Restructure afterMarker checks to avoid 3x instanceof repetition - Add Locale.ROOT to toUpperCase() calls (Turkish locale safety) - Use var throughout all main source files - Use single get()+null check instead of containsKey()+get() in renderer - Add proper Node import in AlertNodeRenderer, remove FQN references - LinkedHashMap → HashMap in AlertsExtension (ordering not needed) - Update README: fix outdated custom type docs, align examples with AlertsExample.java - Add screenshots showing rendered alerts with and without icons Co-Authored-By: Claude Opus 4.6 --- commonmark-ext-gfm-alerts/README.md | 18 +- .../screenshots/alerts-with-icons.png | Bin 0 -> 20571 bytes .../screenshots/alerts.png | Bin 0 -> 19824 bytes .../ext/gfm/alerts/AlertsExtension.java | 11 +- .../internal/AlertHtmlNodeRenderer.java | 15 +- .../internal/AlertMarkdownNodeRenderer.java | 4 +- .../alerts/internal/AlertNodeRenderer.java | 7 +- .../alerts/internal/AlertPostProcessor.java | 198 ++++++------------ .../gfm/alerts/examples/AlertsExample.java | 41 ++-- 9 files changed, 116 insertions(+), 178 deletions(-) create mode 100644 commonmark-ext-gfm-alerts/screenshots/alerts-with-icons.png create mode 100644 commonmark-ext-gfm-alerts/screenshots/alerts.png diff --git a/commonmark-ext-gfm-alerts/README.md b/commonmark-ext-gfm-alerts/README.md index b70584e9b..2368812e5 100644 --- a/commonmark-ext-gfm-alerts/README.md +++ b/commonmark-ext-gfm-alerts/README.md @@ -19,9 +19,9 @@ Enables highlighting important information using blockquote syntax with five sta #### Standard GFM Types ```java -Extension extension = AlertsExtension.create(); -Parser parser = Parser.builder().extensions(List.of(extension)).build(); -HtmlRenderer renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); +var extension = AlertsExtension.create(); +var parser = Parser.builder().extensions(List.of(extension)).build(); +var renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); ``` #### Custom Alert Types @@ -29,12 +29,12 @@ HtmlRenderer renderer = HtmlRenderer.builder().extensions(List.of(extension)).bu Add custom types beyond the five standard GFM types: ```java -Extension extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") +var extension = AlertsExtension.builder() + .addCustomType("BUG", "Known Bug") .build(); ``` -Custom types must be UPPERCASE and cannot override standard types. +Custom types must be UPPERCASE. Standard type titles can also be overridden for localization. #### Styling @@ -63,7 +63,11 @@ Basic CSS example: .markdown-alert-caution { border-color: #cf222e; background-color: #ffebe9; } ``` -Icons can be added using CSS `::before` pseudo-elements with GitHub's [Octicons](https://primer.style/octicons/) (info, light-bulb, report, alert, stop icons). +![Alerts](screenshots/alerts.png) + +Icons can be added using GitHub's [Octicons](https://primer.style/octicons/): + +![Alerts with icons](screenshots/alerts-with-icons.png) ## License diff --git a/commonmark-ext-gfm-alerts/screenshots/alerts-with-icons.png b/commonmark-ext-gfm-alerts/screenshots/alerts-with-icons.png new file mode 100644 index 0000000000000000000000000000000000000000..47da9402b557d4f1d8fac1f1d9dd7b2cf73b4b14 GIT binary patch literal 20571 zcmd43^;^?n8$OI8q9CFoB5l%$NJ&mmKsrZYDkds2GBO&a_wTgH$jBd&kzI+p zag}rjtfTdnjO-4X(mPpQ-;Av}ij4bS?A?0}f3SAHTz>z$bJJdFV<+i;o=Sec?45Vq zvYNjYB%Zy~Zfv@nCwcqzVisluK0pjZAlfAFUR+!}Al0x% zhbC1N3cg`Lx)6Q(;R+eq->6UuGO`asS4q!92mk;7fbZ~nS*9)9p}D8m2D~XCJg zmDzVRww*C{^82-Ng_=y|36Oo-vvv%I)YlsW_==T)VdZKwGa}(6%nKUVp(9 zbRlxDQQ@NT(j~ojZISTJeC}!DS~9?Y3-N*AOq0;}Kw7z@Jj415S!h!^W4Y=#?OvXq z8`cWnpIM#~BzM+an}8evQO&LjB2Ph5pQ3_JdNBSE%mJ5KRWBSK`Al;uE#4uGm#^u! z!K&bS&y^o0F3z&j`HM*$LMR()opAPHOwew}f$+t!x(R9YYwld^4O zE~~yU&sw@alc3>03>U5*VwRO5Y?nCpAJ2*h?#Q;KEKoPogWsn5%4ju*OnKOjocG&& zG5Ryq5q&qVdzA zcSFZ40HD%k(whTc6)(~ZXi+T9O&dkHjN83BU9ong5i5UktM&%+s6UmXgzyj;rCa78 zeC+g#&UkpN(Q4LYsAF=Ykh%^>H2<@^P63FAoat%Q2Yr}6&D8#Oa4g|H>U!42OpiV? zoD-@^b&_4XzZ!7@2bgO&YA-loL#xx^cB||DuYFtTN#OkU*y4bRPtmk5x?z#WEs5$^ z`l{P@h?BFn8bYE=EPA{eT>;tT6(7` z?X)*EYSilkX+9ADI`Fl*K-rMTt%a&G^nOoIl z%W{73c}J&Sy#&g-R#Fy>ej%K^8DY)EkgQ?D^iN4Lf~ zL09dUpuLsQ{!au#LE(lXY56|dy7862Eu)xKo*T~#VY&PeQx&*^mbkSLPBZ5P&3I%n za2c65rTsd|YqJuwGmaN=BcSP?zofC!*yzz%xR`D#gh~mMr^buRysXD7!REmGBl<3VhcQwrR33iL(@{nHk=g!7F z=y1#7p=nPK)CR$|;}&A?2b9VPiHWo);pXKtC$nXU$x5HWXOPI{8w)qg0b5Fd*9N%b z_jK_54{P+0bRprBTKxS%7Skzg%M_)ueu;{jYdqiQ;qm3^3e{!6E%b_-9V%+5Z|$Gk2%Zie=RJ@6Z>NE6AoBHH>mR@dQW$@ANm18{!87R1{ph_{lMsUf z*7dbDhUF^O^a*3p5cT;zf!g6KWRDq0Sk<_oqP*q#QQN4qitHW)CGVu=`!EYP%SB6Uyt1m?n#a$}#+O(dhfG(Q z*2zq&Je&YW?$|<1NRYX6*1UuIa7rKq`1ysoEgskoFZaab!rsQ#)%i@7h=A{uwNvwP z?W$gly6K_6!c_i!`1Z|zJv~b?m#uY{{n4WA4 zlLN4x!zfe~!hNhk9cK`^UHT8zE#!I=n?S)GGl)#ggg?b9(%f_KNrBL*uyd$(AKe-Y5Z6ngd@ z$vb!phi(A>9!D@k)io#I2`bXEe&`xF4_+(;r`bi>Gc8^=^BL&Q2U z)e&LeIiZxXzil_DcHpE5?T{;CsJ#oh%(k60_R;kQf9aW; ziT5bsY58qH*^++|a%4D+f(!~m34hpdGAgxtjG)95LPQ`28~b!{rb(!yWWQgSW_XuZ zHw}A6(}#t|L8FUCPc2wwC&V0fk6$6GuLg_w{jAu9sVB8A6omO|8Gd`V(XU-(ScPHO zF)BPv#&7=}ot+EQG0a1;9lWU<%_6QG83U3!JYb%H_5UU(ToqU!Z4Y;5nqawH`Nb#h zE_zl!*9vRGCi6n#b8Yt5N1qPn8KYvc%%vv8*BBm?9gzPZ$It=AjV+z zjVJh^Aj#uRc5ZE}Oqs;(!|6^B8?Gez*oE;NTf~HG0lTErNcnJ+W8lw9dLf(Mg3l2m z*d#9fUia%vC~xjlZn})u?)8JVFmzvr^#B98q;Rn#WKtq%|0IJuUZg-3$t16mY-xUI|NhpBMR9p86Z`^+q;ALms#rr2vy2$l-7FP@^ z%J+1bd}FT^CJhsHIltCu|7fPEft*jiX>aV*0}@V^X>=J*%c{@qieQZny>hIctp3~EF>2VRT9sm5LZMYypLOZPNDmbS8Y#0i(S$hO zO{4F3NaQ(xt*IhWPhmLJKbB~NzRXrl?L*QVdU~o>AC_pYD=zM~=j zcn9N&&uS@L6{}9t{THT^3Ay(;)h6MJ=EIbN^+=VZSIB(|k0Y_rg43ntPih4Zs{DM< zIt0V!6YR$g$#&2D)QrCs+z_^!U7pw&bDYnV@*CtnMLnnXN@5jVP>d(<(}#iqlZ8?2 zU?IJO+onLbu?98rqyv+WuxcJJrbDJxE^egLM48*_1TTYt-PCCn8MTpUNQ`=WkYZHv zTwWNPsKZq4yO=1DkBgAc;J8M(b>Hz&)CyJ=u(ukGZ442zEWibu-Fp#ul55@PNL=cO z1Xv6$x-GONmg=dfOf{V1XHTiaV(%=(U%X`wo=|wKDxekgzOeTNh_>X^qKbO$a<=i4 z>5?cbIi4$^n#g5XGEo&W_pqI_fDL}+Jt;dU;dOe4nv_*M5k zvy^unL3ONPID4vuE3t5dV1tc%C}^o}d~963F6g6ezGWv<`6W3bRTCy_DLtT{SC3YS(5(2_eY5w= zfZ5pXKp{&T8cwxTW&4YXpO*IM!2Sa`&ClVcY9xydczT{rD2c6vt0*-f#Z0Msk}}L= zxmVI=dbUR^w_Z=6=j+4SO_+Yq-KG0`2Qc$3yhy3n9g%P_^W?_MQ~?RK8CzN|d+$r^j12AkwZzdX9af?~b8-{PIh(cjfr98XXT8dk%J-5DwJg~t7U zRCRLM?~5xX1k)GVo~t;@7F-*v_oB?_1uorpyp6+@3Nj33)|e$ECnvWRO?&WY7GF)6 zkA4?X#aJa_lvH3?ECCYVQOcCuY{@cR4T~`3FjdOU>-7YjMPAv|FTtnr+wSO@HZ~OU z4pZM7?91a7+*sZ!jvt<_8_s*{x4QhZ#EU7-^SM`A;If7P_Lvn1+0cft!Mn`|e1`4PxzH9nilhCF>X*Rg(X2joqxef+hKiD_v5b z2+G(m2< zuJ59FdAjpT|MFeryb|TnuTr$i$}wK`g2?2B`I4q=87> z=_;dkLO(vH0lxhl5#!jASKKGO`la^V^*Kn&KabJ1EgzdLR;D@T9&8%sHyK3R9mDL} zwk5yN9;T0Yg-chep#{3!I{orYw(Z>&(MhUScu)aA{k3ulabRa%j>)6~oTb zSE(OtdKy!vgB=(YWHLvqz4}8%Ttp8?c}Z=d`DBue4KzbQBVBOrP-A%u5J%k6K{3*2al>h7` z29($PwvG;}COk_$x;HIa33(!C~R|86#h~&*bQ%{<8H=|u<`ZI_M_68Ks zte=sXiKCvbh}QN{wBqHg&j#edeQxdLU(J)KZG(D6=fuMTKC}D=`*xtg=1l(FIU~S6 zu%z0x#C<7v#Ean6QvS#m;tBt_mSbMKjT5!2=Lu1^{7mR<<>z`$f>)g@`kYW40ROy} zeIF7)k2JB&VoVF5y)>ezy#RTZXLTNIa_c&q;b&^qn1wksTJb=<_6J~yCcTIbIe92b z^Y1MK96Z?^FN6TCGaa1lEiuN!k#A}<{P@-sZzKY@Mmx2zN$BKZGnNkyyI=0>J z!B30QWJrXl`^8%RvnwgNk=V#L^r5w|Gyw_`a6TqUK!lck7J*n^Gu&)$9?-Y4=h(az ztXUDk$;QS*v{{T#C%-M{Xl7%)M5I8fWWT36zaqWWXG9bU{m&&#H`2CqSjG5PcgljDEHvgslhj{oMbm<+E(Rv8ul5q-I=Db;~usbJ^Fbm z7t@nJ6$=M=yqME?<&JUFqfke}w3<-@SbJ%|lbXca2jiX=4$*Adt#?e7`G?ic`E#oL zc36p{@F-B-Ik3KCegW|xHcqWNKOe`Jkc?ZZ?PP+I1hJ{EaqFXAF)`fBQ(>Y~_l6eI zVj@-uoP3Ad-3r_+%1@#fOV8iG4%)Bd5wGVlnDRi_iUV5pi@k$@j`G+PbO7~5oUzj^gH`j1A{X1PLV>8Y;}=9N)ccTmf#=1d@dN%0m^h3)J#n&l+K zTs5ufP0r|B$|#nRCc9$y)T%YN5FT6fRfi*QCvB+JX-TE-@^At7lMo!NuO!avi$d>)y|wp z&fT#OKZ*mMEyzTNQ~1qWU~IgCwJSZUzWaBh;a(ng!x~>RzM>z)Bmi7@GKMggt+&lxpq?97NA&fG+8P*;~y%wB0p${0aGlP9OEp$~*fa(_O!D@Iz z1U3BpD2XWtgDKC$uYic*5;wF>iqMxaPwfi zPX9E}zuLpeh_om3TFn9w?P*oCvJIxFB15tof)2W9!iVb-CO40E<|F}8jQPJT#0AFm zZ*~J!JP>K!wTj`14)=nXOJjatadAVrUl!>!c={ai(`PW}AzeN8}nR*FwE@0;GAHx+TK7PG#4H8Qeh)Hld1 z67_EgyuApQcRt0Vn0J`Z7BPSR6vR3R-F>2DNIi)oigW8kI*{^}QXnJTZW#Gzv+yQ@l<_``aI$T` z8g4J|kX4^BEYWXeOy0SMV14ldkDD7mqikT}F-9ZSm?d4BTQpA5us;PrzB-ME=NO&f z;43op51-LCy?92wFSYgv4@M85AZDWhV}TsPuczx!5R==z#`wC9;Qq-THNu#z zrD|fdOp_~$lB8YVsSO(n?ZhOIM410?MgN0Zx#t1`|3n}FPr~)GF;~TZ>b+D1(-2jr zoPEn#nW{+yDKybi_4h7#gq!rnR__z=<+FxOgN}dQNO90t$~E{cXaB3B`J71PCw==j zT;ncDkpI6H#gKDtnfACC*~M^_NmW{?L8tzONz(1~mj5PcJmdXB&l5Hln(qnp4}2OD z5<=?OqhC#vL343NH|1=*NYRM=1%%o&Q&N~^7-C#krpcY4SFXoqNYYcteDj_p{pV}> zQ&KeO+-Hwlj?|t1VUXefrNUVNK5R?Wgp9cgd1p;S7q+;CtW{DQv=%l=-EHBe>mt{i z>6NAjCzPsIPkT9l+LF3v;wrkzP8N0>=s>!5rwV1){=I45=H8NaIHQ_h3S|n;;O-iO=P~-#sD>U}|Uat+1~lZ2t*D`B+GP7_`%^|8Csq{<1+? zL)_6QRjPRwgTb`xkjNg^7x2a?Nn%JJ9kvX!x zLyobL$CAK3FS6turZtrTVi87FQQnE8E;oy!GS!i}aInU2L{p(g+PSg8aCbr)DPUnH zWFh*&Fa|s2SVa5R+g$_yyCBf|>aT4kgt?Gk^Rv6{igubsMq74*BC8WCRsU_!N@zY) zVy{6{2IiH6_&D$vJAbuGW&O{=(7I&(z7r)8x7AH?5>T6MNOM)0VcwVRGCzd4nsnMV zs*hLONuFc+Vhc4L3AzfZ|8IFjlDD>MA2x00F=+r)&sEbLP}%gbip%AZ<}~VKA#nbHeSlK zq}<<&SGSWnOu}t*{my-NgW(uYG6y%t9PeJ`Ad0qMvS)a~YXK@-M0tYucOUshoJTLEpk#7;Di~ zI5n~ij#B|Ew%cOEgripCtTm%o13q*beYSLMJ15q`t(10H*x{VlGXQucHiahb|^eo=e9EaqtX{FUv`HT$i(2jZVj z>R;_Fb;l02*YxW-hMZB*8#tSLx>aB0T@$FwiWik-&K>*|eY4!2EOg|NQ|%3qRB+%W znn?DPNF=gqOeNE+YB`LDWs(H!3c1fE(GDja`={B;_WieKCS4l>2t(C19_|l%oQX@M z$_!t7p^JtQvjO;ZA=;$v$l7EKIikRtxq4>`?hJ~tQSA|MuXM8y2X(z0n*G90Jkje{rqkLT0?#Q;pC!ylEu6@!!0FZ4Jxdkrb6Rf$iz)x8#&Q>@Sy4F9=}k6%Hfk z!H_rI!*eSBI`CAd+9LVRt~9>g2z!<8+h+>oD^Ze2Q?gN8lm)dCXHwx^I%8O!cj^ulO??KjHRCH z%n3F(@iT+7=yu^KCf#zP{N1*WqQ-ZtQDcBygqlB-)vLMVRoDz`O(ilxp^psmgPQf2)bYJJ`lSql;Cq#?7}3Kz$t zSadJJCI1XKSgb;Bk5>Nk`6=Nxr$&6+kEaYWBe;#L17!uODKFxu=-7}+@`9t5v*cO5yZnP>VSHu!To?Q zvxdtkYMp*lw=YH7Bh*>fZB$()eNSz>#ga01l7HXl6G=f=bat%OwHvW1d0yw+_2qh} zV`^eUz#7ESctC69Hn(}x$Fn+&cJ26Xj3c2fj23#I)M)?Tf8M!FpFYJj!ROMvkcZsk z`Bde%J9&fW1G;^%jf3=?dNJvbvK#~BD`|KpOLP5(Ql&}_8@?l0&iMz8?dKT4YB~m5 zYAQkP2^u!Q`rJ;#wXf zs~EVSIfD5bJ$3M!!u3^V_T~3pqF&+s{~CisgPRD|JA3P+OTSo1sEKZKYSD(@+!bq7UxQd=a(gi! zFX>D0Z&)-;C-&L!%UB=csjhss&5t)(lLgt&x8EM@L+EIWgd)4eL~+yZ#C?&p!s}%W ziBFvSEOZn$oC+8G!Ax*w+_o3uydbnDa7R0ngAh25Q18F)UvjK?q+b2^HTFt|M?8$?%Y?rG5?b|mF&t>s(lZ!Yqf0v$~p=fCAJ zVTw$3oJZ$h5DUSXOBe3^_{nHvDg$L&(nh5|aG`PxxUspST5E@dftAgPGkD0c1q~g- zY80~4tIbK<933=sJaLlByvm)jnN#w8>3XcF2H2$BWMlI{kleAZs4$#nM)XDMUeGog zRL6H)irv|=*uh#PyVj}O-`U$`de7y;WQl~J&w5-+@sBgJcywSU+igH{gr4BpB8VYD zj>*Sq2R*hek(X}hnWHVQOEnD-F1@N3igLJ6IfkglJte90;pi}7c%(ZhbvNezk8Fc$Jf?m_@{FGqmWh|m=h;5rXK(H7%XZ0Np%}5&sRyxlm z&4yvdIo35-{QywErTqRwn@}~K37w40noq$Ac3MCmCv$=&Yq8NFmZ#TGhLmQ-9JcZx zXNc?4Cz5IEXd(;TmBUN;>ME7O)8RudA$A3wh^asSLwqC-U4wYq9u^GbXHSPw~Z$@kMwh-8Zj`x z!W|!{S-qkO0!LU%VFG{I9bWC)6viIIN69N(yBSnc(NtO-riL-M5-{#bJkkP2j}7pf zyCq?T(%B|OTMw71?w4wgiBPH)Opje;#=N~uKRBRs;7e;y-~+yrmYNUL_g%&EA?*Eu zqla?a7Gb{8fMZnQ$FGv4@eBwfB^V6>F)z;0K~W_XhC%38?E#rrO8wyxJ9PCBGpa{g zl^u`;t72BQH<>?yy5(kR5A}+~}VyR<+u2Cc7$xlbsduzKt z+q;1pl`NymG4k5%&X)$3Ow|3AsLQYS2|Qq@NkjH+N>Ho*1L&PNL^>6Nv%*mt&OiQzBW;~ zm_%@YYA8aL$v{&Zc6i~05RjD=D=8`+_qVPF;?la(hk390R1)m6OGZ zXY`JBX6U6Aq7TjRjO4mM8mXdhdUJIV6ho!sX1JU+gV&|F$ofGBF>fePNT+(X`P{lE zO;CXkH0H`6lA}z2!y4Kh%N9m`c}SMpmy|PpP6<>NNP}lvt&D9xi%B_F3l-<|)-xHR z^a_H>h3+8C1^M5+@kBO$v06-!ODRqA*f@W_c9NH*Xrq{#X=7*G_&vT*>Fx)iK}81o zwjq$XZCGeyen8!}xAa7rx6>?}RYFDPntW*p-hxoT^N6^AFyqf6?%wn%J(R+&l&68c9pGWIW-~C%F}V zukit$jVh}VqN_@<2rbfvYS(&?o&6GCg_8(CcZjemw)C`m%)Ua_{ zYGV3DYR&uT+Fv0D@)!3gYM8r8YzS2%c;=U$RiblUJU_W^3471Q6k$Kr(VklenQL|$ zigR0N=a%+i9Rq7MarAYIeKo4yRMmeWL+tZq)kMA;tXac7=mGC%$)$@c+BbqbcF;t8u#(Vj@Z(ue8bM&pDtzr2h%jL1@M@wW2My}zYzFcN|UtQ0lw>0b2uvyy^xoLabx_GLj`{(mP zM{h_TCs(pcu8VO z(d(!3Bo`qa@>y>zp;x%I!(wUTU4PbECcmQ??@Y05@p!`#-}JTZme^7e0pqG>33H=r zEVsw~X_P(KevwnTe!dVC%li72!>R#qN1Bgwjr~!V#oTqQjW|}!(A_5J{Pho9T+kYf zr4fh8iS__Q+T)P2`i&*Tov9>eH8v8d1GZIeCQm|1R8^@~^XVIVd%R}Y zE7(#{vpv2y-j!*j@0yd3Y4Ec3_318T5MlA%KeaCSfP2N6ye&2lxaLYlI^On;Sv| z0-I!5W7(x7Li&aqxy7nC=30+x3}hi1Bvwh!A!9#VYzo%%FQPY?Kab<@pDJgT#5E1i z96p?wy1S~ZdzKx988Sb9xM{?jY0W!PoR^@3KE2t|f;EBYbsCGgsIdvQ4oHRUeQ|h~ zc9TS@NGTLn6n%SdSwO3$#}G4fzw;moI7?2pfVR0@j@ zi_Y&#nq`b3`@M<|D(ua#|7vnWf23D_k=me%l=Q;RVs2d#6>oWuR|}Zd44%xT2T!OY zcSN2m0J&;R^)%DG?2D-;IoT!P@uq%tgk%`7zdB>V*x7`2Pb1y4HUrdk;ysFo2~E)ec}5%&@aHRu-w*u1Ljqg)pg7@LTHb&HT#e>AID?Uyg~AN>=jXeT{hNxrNn zmR(<*lk|}`r+o0Nh5_%4`6F6doNo$uJB7WQ0y;5E+57$)Paoz~_+j@p&v!MqrdK&B z<~pY8LE%2KJv`Qgz1nk)#KA$Bc7qVpC|qu8sYr1T*8lBwv@q-0d#xt{>vfxUGJfZO zy{@p3oh#;7#2MraV=tP3oX_yFKPeO|9zBcyIb=J06uY*5jd5NmbjOUth_NKzkhDLZ zUk3_Is_!?G)9;%-GF9>ft@9iV5I(6-rP1%q!mB(dpG21#K$YFbXYPUJ>g}A}Z{W+* znpKxlG-fw6=%w;OO;PwmX3hb zkY1D0xl6hRqdsWCNu^&J21eW=75hKWd;dR4oAd>yDZ5LM*-qTnUuQOl`M-*pCbT5w z*DUO%S*!maC79$4YffK~)u9jkUrxSR>PxnA-PSQ8vl`a?g6xBa8`0o;@P_cmd-1JKaj4ixI>rQDa3QS{KSJX30#e+n%- zLznTld(z#P&v}k0L4V}eak%<`^Ajj)M{t8j9x*JZ(MYwmkl!z+2bLbI2Vb@;lBCj; zZc-A#5nf(@#;4Sqo|wtEnxkwuWQ#W##x1Pya!MSJyg4@leq;y%{NT>8=d)wWs??Yk zY_D`S`+b@`LELf@X30rH-)<~*I=`I9Xkz1M9`k(G13bMxhrPIV@&VfYTihMf z*nI6@(_s$n^8Lr&i?Qh`zm;EdN*s;7snnRsB%IAi&Uni!X9<7876?+WiqJ{D4qDxh zYN!)+4)7!vXrMDb^(7tp2^8XT{@7j*WR&q95PnMOz=R=&58b-xlXUN>whRyO3%~%p zRy~J5FXYN2BZ?7z9^1IRQvLdnvw@m0gp}VrL#ZbE8LM$v}K^=*1iYB3x9B1(|CpWMw?Ey3~8Uus$ip!jeAcIcY9$044fKG4K_=En8hqZG==?- z>jYHKxgE}`dmFt-)7e6OGyRH67)w4K5^~j(Ty59g>!zA4@3WyGk)53#;(}$jvUqlr z{z;+a_D@WEKzOXf{5yh6ECe&s@)aSFu}}=T2DIb zzZk8u7KQrzt{8pu{p8|`c40#U{GTYFRl^Cl*DVIxPKCR0vlKKSR?%s%AY@HG@6$rH z#B3L@VaEU;l-WUCQ7Xp044DcL0dS@c&KxcIX_$iR>=evcd??5(0_NC0-I5IPMTc7IUq$^I1I z1W650j7YH%x*LJcCyD8yR#$t!6qda zV>=aWo^?_9>7TxDZ*k1T7w^91WqsV%U5C5I#*VO|hq^61DO6g~c@)cb#`b#4AY7W@ z1uFk!isSl?BnTm2(TAVEFp2uTW-2rlPoS+XsfeI=SH-Cs`FQUjgKpJPA0TTZ6ND># zmfvr_Ow&IlNZ)dyK}H@%+-VheTN=}-V#72#`+4GeKFulir}cgUrq<~vR2S1tLM=$S z>B)50$@Oj^)O5#F1U}vPxeRM6*+1MJTd8`hND(X=xRqk_=wsWpm`iED{bh6z0x&Gu z^-SF=Yd_|mJn%WqEZ|fz%BRY3C@%5mM?vZpo#jTDGwM%z-3~|!+&c@%efZdAtB`yz z>%s6j5p=B~&c6Fr|4T>hf-oH+0nnc(Q<=_97c4T<`|pjH4SL1yUBK$a?)|d~E8ki} z^PO^z&1(mFrA%5N{&6mk%iOt%9o#^^Dcfz~Kw`2vRKWCTAi2oFdU4{9BWmDbV&=e+ z!CgAo-4(EBgd^1(pIY$}z0oSfcD!VGgt7h_e=Un2FZ9U0t@}_ASYe!A+n~_s;L4NU zprD|hsEvnH#@53l+77v{khS#RNM~qBkIe9Ng?Q5*IwcjFi!oI?mgidc<^_n1x`1c$ zLcJRC*>;An6jbBZ8KHe?8>_C&kO4!xBroS)Dj^x|{b!pG&zkW&(LhH5HG;VIe%{^1 z?#Qfmt?|a!evLbp&FORg;+wb3d|tdDt%#(h07X{)k8@{1#?4HL6MfBlSP4{u7G~&! zICKyo^uagd)$<@)Hux(?_`mRq`rpg$thc_;1KvN|o;^EGV&!?4xg%sid_7FZS)~8= z3bw0HBwQo&<{DjlHD7%7<@*g~azS_4pV0i0L=CvM|3h(82W0ht;$<%J2y8lx{j13O z$2!1xP6-u$ZAn64CL&3^9qFgy;yMLKs1Dw^xp+yuE0+(;`x)=;9N@j%;~1Gl+A-*G zU+O(2BNN~zMVXSK$EBnpJQN7PT^Tsll6Y z*(+Qo&!deiRL|2A2fr0qS&MH1W3J>wQcm5?%_}67rzb)}G)McLvpy%^OK56hLG_eM zGKyH9`2zb1%Y*sh75xeIO=~Sk3Kg|jZNX8Za;0x#ze0fIpG8!B6I{@JZMVrcm2}9i zMP+~gQW0)-?zgRcfbU+PSgpF~&zfz*FMl>aF%SfYAn5>zZZVhdu?(n$kz0rZ1_7e5 zisd~~2(wH<>}5PBr{r;c%_o&F5}g~=9pazOSdv^3a~==c zij#jeaQiNwQ{t!~b{31YNO5LVjn5tO3W@PAAGJs8?T@ zeUy%+E7Z7rX}OA`;~?dScTgq1Fm1?Hlkr*jawK2~KdbBH)icP6J?>?|M8VxQ;^>W> z=NcR*zTJ#vlcqYK$ReloH250WH-`u~oC}edt?N9l>*xhsjM&HopZ}%mBr&;~w?pR5 z>oV?VvwnozY?Wl+#4TQa;C}4asqt`G%~nTkq{wULR3(`rI44+A-*vXSwHpk+Bk8?w zRof)9cF{+5e^yd^y+_Kru=-?`H3e>bSz)MwR0gwsk7FPKZ6T?zhgJKr6|WKI&{;wJTO zle`7Lmg9K_x5B7Jnq9^@h;cG7P8FjbbZB}Dz9(_MHfhZ^oh=3x1SKRocrp*x8^HQWz9xUdj*w%zQHp1pB5 z%Mw(EX+Ay9lyqbm68Oa#dnCtpa+(M>t+OqR32OAN*d2DzEww32kpcE4)yS1D1OcEr zH4X)JV~)O?86d#r$G>v`b@}Lfoo50!cE?F6JjdEQCVMk!7rX3=!xAd zBIV%>htWjQ4qcfULbgVjrrwr=h(zV;ti9@tth1k99;RLcIE}vuNp)X4-u}tPZ>Y$_g4Lqct@+<4pep=Hd~4JEL*-Q>eA-R*D3E84ugUN417)?ZWrJ7Y03tk4hN+Xj+S3M5urXBz&$8?ZptPsDRszUizE|~inFoXUM=d=Ka=SatgFF)q3YzYY4lems9AQ!t9gqwvi z&vKYIc&pK9!ZdZ#kfPNbM(l&KSiMri0~LKp${v0PgB$r%SZf-P{h@)XMB$qF40n~y z8kdpDxLqyi7|R}~@}*jwT^Cy1)mJ`hc{nY?EaqTte|uVDTN;%>8T(pP%`rbXg6Yks znEf*PE2T(k#5EhH8vi4=rAhGWLX_XZ(eC03Nl)6Uw4d>ot_ipdrk!D)J;_ZhJ!E?| zA;Qc0YH%~QDkLqu6-HyCyF79z=7JWcZ8~Dl4u{ypqh0eICnsCP=n&(+H0wF&XGsTx z1+cZW;abzCYCz2+7&!xf$T3h&mJRv{W8}}fY1?`pWp|W7CLCQLd@veeKVCf>am0MB z!Ik{dv%dCu`TXkf07$m~w5kmBXRI?aY#G?fzdpG=a~75zl*@jND0idM8k%2(*DscR zOCyFIQEJDiWu7e!7&5uRcsbP4A!v-)S4#Pa`=i8~n@ioe^H03-kY~oL1C1oU9pz`x ztoay54>d(tkdSRQ9xCF{_U`DuQSe_b)6I!9{;86FPMh=I@sM>J@vka5`}8<~?@O1z z3@ZSxV!GOkmozWq9aj3+2C~%1j~sQOrumiEMV+R!OBBFMoEpW7jJ!4jlb~SHkF&o0 zemTGes>D@(LnEkvO)QhhEX*|oa__l-Nn^q*!~I5NiP4aG{ZXkt>aEu;S{51irB!~E zcEZ2_g|NhLyEj^v{V|zpSma=$bW@x1UI~)nqSAypcY5ZRmN{=bg-<5*;v@WWK6u6^ zrc~O~M#MB~+;+%8t-5V9H;LEqBgw`Zg$ThOQe=k?T#uR-?`X0|(r0DHD zm5+bkg<)(#1sdrJoD)0bl!mS2+;cD6p5eFt0cd&wT4;nByw-3#h&#uju*Pl<@H{?4ev)5m*T?rb6hKMKw-ktreey&bNMmzuS{{pm(sssHhc#wyb zN}&|o^;piHTaNRaM~)B+&_lt2>)u;aD$K@Znyq9|U_iA_X%ZxSRpX252I#>wrZ-L= zpYG#SM>f<#-MurWm?Z%3JlkBrel^#A`F(^^S*@$L&Fme1) za(2UKFWR^+*t8DrQL1Wrx|f<+oS)r+sjv&9Xt6Jzi;}P+VYx zyWx;R9@RYw8hvhKz21Vg>{i^`FsdecY4L2SP~FeWgZRR%&hB#qm0}`&BI9i*1J|^t z{w4B-Ls(Jg+q3Hl+VYV@Df6!feNKwxGdwpE1(2*w?%O3pHGSTzo@@X!sZ7tr^8_u= z7*xagfkvZ4^Q6cAAtS^+Qsvw3Liq6xvcO)a>a@?ngF%3C;!nUYM~LUXY%~+fW!9j{ zEx2U4*K`TNCZ)+~Nn^LReu%Iq9G^FM&@zel-Hi)2O(i;a|EyBTr#e+m297X}Yzt-X zqnys?+>b+(%1nxf`r{Hy&w@99^zfn}z&m@SOih$p6_q;)SgwBeXJ=Y~Ex&xKtdTPs zxX<@+40;?RT@G)9O%Z0a^z(-=Ta=85e*nWE^E&%@^h?rNOPUR&&P{Fd{0REdbO+)p zziYk-`uv&m);f;kXlr6L zNcPNc)Ojx=(fk-&dRpXNq;D$iZt!zZb=MF<`oDl&4cGpsiQWFUSah4+9;2S;xE81_ zP2VB2=yGH#?@Qu>jHfe_5MYN`2$*>KGUPi`vG(hGZ~9-D(kpuEiLTiXtBU8T?kz%i zzBDW@w=A@aSK4e(!ZR<<0RD^LN^}Ys-1f$-v8ToU3lfM;AJ|S1?$iYXo2eKYny7Td zz#ow(m2TaY)Z-)CpwbM6u$G<&2LVOF8COTbB4Y&jMvB*?uif-)1WImf-(n;+Cx;Ag z*i`#@&9K3VajvvH&*Q6i_NE2owk26wfht(SK{0E#g5SE*YDXjS#c_3m4j0t2M$8qH z!wR=2h&qQU>BouLs)J+I?U^pJ-~ZM#1!!RuEp#K(U)fam(K6MCTz+1(srW)6JuKdX zB&8HlxdgneWS{j5X0GR;W|wIeeFqzoF=b7bFI4}Xt28H}V>7HbD`=hAcDir)INPU^ z@g+lC!0q*{*kppSBf+H`pE6Nm(knGVkQI>{|0B%sxY)@C?G^VZSn4?d`Ppp#Z6IkL z+d!?n`mBk0uPd?&GUm-zEM$#+BsPB(>lC<2TZ=Y^_D?>e*^40#OCXGu?J9KO_`GkgFwxI0fB9Fz6Bcs0fb>9~6bo8NhO zDoMd69&CcgJ>XrCk#)nI+!=02RXDHzbSZ_ib1i;&ptbhGfs7(6c(YzmLdA>N40VzC zUDTR${4v!2em_&-c|}9kfDpLEaE6RvXu{t1YK=$e^J=kKBEibFKnd*r<9fvX9#?Uw z^fW94AwfFSn}Fgdo2uXIrR%1qh9`F)WPGKYJ^pS5s6W^hy|HU?gT6Ga7Uzb$Ae!qz z_58=%htFs=XS8gC@+DZG@?Ik`GUw~Lijl_y&WC}dj8mhMqz&75+#@0ijMxp;7>Vh? z%`ghz2D$X(*rN=o0se1*udDY;dqRMeUjq`uDV)8o`y{@f>n@FD|M2R_uW;ye$~^&Z zQ{)GZY5sl$>4_7x98{(^4mm6CODfSZY8@|00_&`GY>e{^rlnO_=*jf(4*X00RoiLR z9)<)(K5$4lZ}#0Afg(ojHwJbx*$TvLli64$s~yp^LCr&fn-<)r#fWSS!d^RH_xDq; zm<%?Xm5Wl*-MX!ME;B9(zAOo3kkxX}@3K2sv2&Gt-ptRQUo;Y4wpNo5J8?xMT~r64B9a%_;8FZ-R|yl$b9}9?>=baJnPWyQiDrVD zH;=#!|DS5k{-5dnjpHIJIay^W)lL$*i6We$2}yB`WTfS$)Pyvd(A-6h&Ky(}CiipS zhTJtnq!Jn0rgc^`ooSfZ7{-QuKl=Im`}gtv7v9(7eZ8Nr_v?CH>N@$20|izBD6hY8 zyuTNI_(;Cg^5#ehvJzIfUHi0~_`=@7vyZi_z?#xpr}rY@DxCbeNo@_lVrBvS5jt(l9DJX8)-thWevKTvRMf)cFbS9#8AlJau=w?5 zK#IY|XWTi;ebVgis=j*nre`JH+4?fNeDyL~n@S;LBKZE$s%rV>#n*kA7%Q? zFHa?qMO(QB*@n*IBosE>IKL$P&T!G9)dT_!b)K>y`OOs~I^S!n#{`0lOK%^N{3NYD zl-qG@FUy#ykt2S4I7E9Zs!L{7-YDj2OA8lN84|D$Ps1ax?&D{_oO(0fsXW%(E*tvp zm;uRs=kO5Zn`&0rymkL-D{}{>aqVJ4tRenJ4K+G+62O;g&1Xw#mTJ1{uIOujFue}! z7DKFuatyA243@vM%J6YF-F(Xy2VG~s3zSHQDOYGy!NI`AoBU4`FOd6!OB-Fo+cVkL zFD|xbu}Yo2h^D`Fxxd35DmC(3Mf7S55GFdWrWfiU?mEuF#^R%&Q5Z~)b=z<9M#o(G zvhd+UUg;SuaIE0u0+aDZ8vkA;KhNcGFBKklqE6tiDylz%m6jJrp+8t*yQ6%ywnm& zUzAbCE(_X+MLdI{7J%gxoXcJ5AN-I5Lv`=p;&zF+x@lUB7saLoa_)1V*68}Nb5AY} z^p!|hTNAuH{QEKzRY~4=#V;jT=WJ_@0~;R1d(BP~csA?Em4ZFT0eUY_PLiU0qlkGHxj-xZ7hc^I^x30A^qAW*eAkD)$?$E&4w|z^ zubU^L14I4N%gLKPUvfHVq&}1X=K+cMf=R&Ku^z{53YS@B=FDn7BhRYG3J?LZ#U6bp zC6eLSB*F#C00Pi)Ipgf8JO+__#VPc$1@Y$e3D$dH)>lRA2nIUh9R@adr%i1Mb7zJTIH7Sn8@ftBke?jP zQax4_R<2Wwd!ZbSNu9BZs07e$xG!6ECGG0|?^kZ^U)kjWYkHo&51*m9QEYI%XjGlx zLWgJ?)Gp4@ynj?GevJR*D?x^V3R8yttX84umAiGaM_fot?-TvfUA1eIm5B=9D%!#q z_|NuQ`5b^7SO~rVKuCtV<7cUs;CDvj*%_}a-i9w`4|6>B8owG$m0NP%%-!&D->J;c*FIrZS|f- z_Iv!>eyBjYLQ!E{X~cB6@Wc6o0qn+z{5&7OR4{;`%7o}*JLQX{#%uWG6C6jJ=22nt zjT*x&i9c^;*`T{}96WYLQ9O)C#)kD63K7m~bspyt4Y#?ajv4Sc27q_maRabTXgjewMi}fdC2dvLZeD)l48j~CMSdfw~Q_9oN zjH)7nZ26-TEw86^2<6GTXqkWMrl^^b1yRg|*bd+%h)yjm4ESk?=ER62&@#D1%I_Q{lXIIci3g?05gJdpWj`y=AgW3n z`mxKB!;gt!+rbZv_pL~2sI<1|JOZbo_%_k_swq3tWx5Zk$C@B!5=)y-C3c5 zY7H^221bgvyD#nj2_LG(>YxJQ&)~{Ykjb?z3UY%WlSHrtSf90#Fo2{PrH2=tAR7oP zLx8qc&`}sqYqyUvoG0HFA?I!iHhM=YuQsBF^R1f#DAi2$dm1|p6x21y3Y*WC6`c%8 zFoWo7uCfUaX1O&ES|{jFftGrKh%i4|xNKsMwKL;UrNRCQG%#q$srH& zzc7D!p;(x&FW^aaCamvaJDRGt9?Gyz)$IM{!(&QawytNxMt7B*X|O8;%=?mS13B>{ zj2s8$?Ldm`6v%^u1NSIHoN9H&bK+m_w?72BK5p>;v)t~#?BbIG#gLQ9daNkG(a0&S cYkdW|ZEodeaetp(5A?~MbAX*D+4(2^2Qhz?bpQYW literal 0 HcmV?d00001 diff --git a/commonmark-ext-gfm-alerts/screenshots/alerts.png b/commonmark-ext-gfm-alerts/screenshots/alerts.png new file mode 100644 index 0000000000000000000000000000000000000000..83d4009f017bea3a0fe5d9fcc1aee6c1908964e8 GIT binary patch literal 19824 zcmd43XE>Yx`#-EhwGF0ZVpWVlCKe+Gv_xnG>v9-^Bk}9b)HuuG}VNJuD^-^pu}klc7o zLUJwo_I2VnWGoYx=RS>M{CT6ezZ;PCB1 z{!MuW#&sU$^(~%ttZ3tnt;_A%4lgi$|FxyLeP5~B9&rf?salckivYW%s-U2tTg2|m z9!?Oi!3DPsi7#~QAFh#*7)A$^l8}7hyG}=J==wju;q~&ZvQAVBS!D|xds1u7KK6*X z6!oX!d{$v1PaBz@=-j3>R53UWvpC!rAhulJ0|9-dL>?%R|773_xdnexx|L8tT&W{< zVDvYR5dA8BHh){iPG`j zE*`qmz)CG?!9=`s`<3Ud*WooX66cRO^h0_Ql6HyPE2s~CZ96PgYlFq0?3-s> z)ak5cQaC?sn9uo|B}IPJXn`qn+`BD&EQrd&8^xS67~)u+7iu>8yrfBJGM|##J0qxY zOVZ&fl$%!KZSHNN#Qj6^v*q)1M=F_FZ=6y4<=bQo3;d|fN++bs8OjDXu3L5M+^%f( zT|fTSa~y$6PwKGa4PcY1^}0Hpx0$_z5@tCTB)CW&mUaAgOx`~Gd(_uLu))YS-3`-{ zrzLslN@$k!Rd(1f4`={(BOpIkR%nUsQ4+`q`q6S`c5v<}6^hp?7?Nr@P<5bcXz{P% z4%C^vMc9lz=8~bUIHFW5Ux>1age0>k&HXDZw-ML+5r{{#k3W&?nq%4m>CT5(Lc@7w zP#&KjS(YvzXevQY7D}--Ud)XLlEccI%^&dDqWYr9PJI^8netteG2GAoAxffnk;t72 z8S>?YFreSarl*RGH_=O%rRQCh$h012ymt0g%3gNsTtBUAh0FBWVUGN%k!#GNFgKHy)!rudEazpSGTdud+SOQ*zl}e- zNWN1V*NR(95L5om%<+_^@t_6s+mYdcQlo3XQcSQGdlTxRQ8OU30kDq1}i(<-JgZ3RZu8`)gLg~8ba zOwd{C8LFQ?&u69a-)^0Qp6rDWutUXG*QBL6V|F!~c6an74S^9{~!Xiw7{92_2 zT-ZXLna}#3L1bmG%#HG<7ww(-`;<8(a22 zbDPs;9n&^ZH;%w?AQj2&w&5xHHG+ZY2^-Ody|l1c3QVmJm!fx3h-9Af^yu+6WK&mE4=UI}nxoq1a`=Q&&N}cUb-DHn2 z&EZ+}PBWH7N!|sLGW;3EQo2fOPjU4zA6|F*54@U}KR>(! zP@8KajW2j#9=^+iaVQOoy>p2dsF?x#^j>@X?8LXSW4hVHWR_n+TgihrLkw9}*(`Ou z$4~H=4&<+yGw7-+%n zmIl%TGvENY)0g)6EMMy_LYg8dZKM<^a5(Z2_1esn(4Ae5*B3$^ta3bL?yvR4Y($=V z6vOqsb+62^UXm92+!7wY483&!q_--sjN&x&AcgCX9PwvDX=#im{VTm^o57VVXf=w$ zq~B9A??=Z45sI{`=54Nnb9BRvX=Rp<98CC@LqSKNM?DVr6S$R9*f2TSaWQa5pW}yP zZ$>t9|8iD>(E!~uBfk_*wIh;zwh}3JJZ>tOB3#yFAqkdVF6Eh~O8c-GPkMZ{>vQcr zFlX9+`d3(Fq2Jf2*Um383q{$%`Pvcl-TyA#$Y;6{*|JmBa=z@;;#A6Ou2ab>N zMGI@K^5VtJ0elCrHze~*``>T9&Q!zy;9Oq1u=?Yp6O~*pdeJ=c#owEM!{D};gE1JF zavw%PS2351zzT6O{ouXaxomwklB-va-bs*;IMh@cY#|K@Xc@|uK3(2r?-AUdX)BTD zvBlx>rPl<7y!`z@kY->j?moLB0C`xNEG^whI1u&U^KEBn1)bKs&6^(Ujva_+w@dKO zRA-SGt&+QwNh9>#pwisoX{$t?WDVG5^97ry@54?yO2fI-xYB9cQ;b0ERHpjIr^7f!o3{y=UR;MeFYRzc0=0P?np;kE)&ZhET#3W zqg^QJgXgLhy^y!H!_&{zlV&!q#+L6ewgv4A_v#dAk!;QtluubO*eveIPI-WT9?2sL zOrjK3R5>>sW+vU1)_=x2Wv?EI&;z?AL|jD8IXwZV7HxHA1>;3;`%@qGK)PzLpmS1z zdyV=L*HJtc{3tCGuQ43$^!oJS^t4&Jh-X={c1eRqiHcsoL1{r&B6&asa1z*l54cdy zG%>-UbK({JVm-(9<0P-DK^lj4lPL@$L~B&%GhmOmLUSUrrM!Oexv@OBjFn2DGGJ*n zrmt`B*7&BWBaFfRUihM-b?4#B^%M+i_ev_tcZs8j4H>n!n`S`WBj*MTZ{W{ zi=6A;xWiqNlaqjawGi9;_RHf&9Ao)~MlbP1nhRo(GEWGCssW8SA5>S`6b>34}nPFrx>84O4 zu2!efLhD{;hPHK#W$%QDPq@v%(bV}N8gcZyCyo_&I(C=A?y^Dr{bW^FpWgUhPvk?N zvqe42Um+!$d9he``yn>iK0P@T&y2xaMIq-|*(dMwily_%KE}C(p)PB>%G1GH==xTN z@QQ2bR|IdT%x4OH3h6!y`(G=P*ZJ*OFwKNP1QU5u71DslW@1As>6HV&S1Dt@-1Jut z8F7tgUegC@Ws%x`{?Qj_TA{Z!*pBJXd=WdQpir7Str{>q=IRih;Y>`brBw%nkCn1wi9kf z)-u#gr@cmWs~}GYF0v-ueyX!dImH&I*c5Upe-n+!dKoC8yO{{^MG2OK@op0lAGl3H=;$r`rxn{LHYS&vB z=;I3A9G;p5zv$VgzG#R~H&%{00jRnY8G~}zhxSLBNbX-96A;YXuc!bA=6nHevfC387xNgi)hz5Of6y;^ ztZF?EOC!i-1S^x~gR7X}+y6{kTUT|}H+uuMCBe!>oqY$O7~B61I6IWZa25QKL%Za# zPt0(p&BhV&{@bgF-D~2)kidlj2cdSLl-v3rL=VJU6!1<~IptZ2C7kuoh*D|g=0<=v zbhwjnY&=m>2FOj5@W~|XpJdvlX@%Ur|3#o8d92MfcA9e?VP3DSGZtA|C0MLfWU~*} zyP_$P(Oc>EE5h#{4v&0NH(d|t{5gg{%3hNSh~@;VSYJa&eM!^OBnnuzbkO;+$x!Qc zby*J6Ax?_;XZ20ySxcYH!^l zVf#7EP$hl!6rM$YF4#G;dWc8ugU@AIrrfc78_;_dW(|g$_TuG1LcSjp8ZtfiG|{QX z-ggPSpCKbX0maozRTtV1zyrBaOX2^}1|MAs$dChsxi8v+Vopa1T~SClw{Cxb__=)2 zi`=A+(Zte{Ydmf>%io_W2%|mUZ#g&QEk?9HA4xTS=|yMJ`tj$KJ75y>xF4vRB9?yN z$+mU5W476aMO(FaRotTCwQfyxTIvs|=Pz&8`N7TfPqD>xJUuZg~ z`)_z`X=qSr{V^?Xx7QjuGK0?Mhj82Vr#&>+FCw(Uk6O=!>_-~B6R@@5l>CYJozGEG zcIG9*4$I$|{q_{%?hB}$MH|PveIb;r+QIf2kJx1Vnde=v2M=?5nlEQ@fuB4t(z@`U zZS)qh7+E}PD1R8Io{GeY2v^<~b32)z&&tFLJL zlH0z3>Hwn+vN7aHM+>;Sk%=Li8(bZ)SmiLms9<-XAz;g?4arz?&ki278-%PnEGatt zAVRjK@QWep1T2rH{b=z|BBFhzUnp0&A(k=MmkJF|12qdfYC^1#LL&tjR27>o2Rr1mE}WjpmZX?vLTUyT%dg%`83*0!-#uC-X%J-$kbPA;Gq=G zNb}h!^hOg0WjsoMmJ#m8$4+nRgf2Uhc-{=PFJnJiPw2@zJ0$lc7ni%`>0qb6Fbb<( z3Of+N6mI|7?V(~BZW!Io=^=1u;Y>KMZB4fPbi8^J)@s?LTLAGW)X=i&KWEfzH8c-g zJTh;(&h(f28D29uQokN3}$gt$_#4%B)h^&sO8 zRYa%1&nA&VC zC>`NU;yXmB^Y+DikF1~~=^I1YVg+}L!i5cbD#=o0q6%`3`uk!mppKXlWwTE6y^*ie zhhb6!7cEwuld^&S_4Q`W$&ex#K=@YamNMAt(@mW0o&`j=&S|(H%SSHKjVhb*4 zSBCTOq)VulitFibOD|fzY{DEW^#91_vwVM<`+OjoG%{)2isqI1Ust7W%B_)ux_=3eNk-Lrkn}hnMiYJViF7?XOVkY(WJ`upMLQqs(tkYH~-}oUVoO5sX4s zT@m=L%OrhW%0fvRp|tQ_Y8`y^pzGz}3ftO9CmDc>;sD3B&(jrb(#>a8XU~dljog+s zF-QCmZ-yU2e|ZI9h=H)vC8{d!#Vb7=|&$B(eN#TUz}NG<{IJb(4UQ) zWZt;%mEvuhz8ge#`u41xZTq6c^Z#|8dM4&xF9Jbigihkfe_ktf!oDKXO~I<)J0Jba zKDYLF6@DdBc&1SdnA@px_U(O1&wdS7#zSHi28bO0wG!{dSMm3sgxq52L<|iL5&H@D zYK*!^r0uM3z9xQ(x?e@SzE*4}-dy$Q8pZ$n20Hf}6V>TL;}bhQ5;*4)>&WthGCKJQ1{*FNm>j28sYQCbob*Ug{1zi)q+ zvnO?2nqv8P(VSNQ`Sp)V;xxhZKKF@}{@+b049uRKyvOW#6lLF<0eSWiS9(Gg@{4?8 z`{mUdbRPZBTwna_i^i}U$(t72D;MYE*{d=U_y3+kmGP^;{CN-$hMeieKu+515T+%C z7pQ)Ngk+v=9{V)*Jn8}EpQSa7@Q3PM9`h65x#IwPZ;{MtwJwPBiV5~zsse9kI3*j3 zJ&BlCk&Dt*e9FFjCzQ7A>jL|;$is*!mM2cWq-Bk))hvPes2NM2Y{@mFYPGbR9H4XY zQoIh*>P zY`ATovj6DZa4{QSN`)FkSsmUb3e)i(A4cKPH*&LDbRe{PsO3ed4ge_moW*i0w!IFzVIu<^QJS?HvifGwmz$Vn8vc@?A*KbLcgb;Ao6dozj$Hj z885)Smdp91tR=tS!hfelLHjx#CXw9cyAGTL=O1h(5OD8UMIwmn-!U)#FlE{pSg;3G z)2rv_ZRJdn_W6~v`dN9t?b*JotgP}tOtEuhW$-L zcPBmEk{vG3o<*hmg8H>ITCx-)?{Szuf`Eue^5x!(OYD8Iw7+9@NFN0!;u4mrrPvhi z(svMNR`sWcwW?ci&5roqF!F^Q@#X(}z52gVQ5e&S#qsuxiKJskCKL3Fz|Qy9@KyE9 z)}UTv)R#>|xK}?!t(#2LXR#~^S1_bX-)Wy(1eRFYDsK)`s4#13T3%Ivzds*Ap-@$W zHB)ehW*BStp4aZ`!HCXuR;5KN|K*(BW)7*S+fK?glG`zIcI`N1ng7-aksmnN3rL#g z-aY=()68m6l53a1v#OpYgt0~ZN<_LRvta^vK4lV2sl}`Z>Nq-IFtW|ZAtdT`&4*e{ z@74l3$hil?3S5-^J3Lp;SN5h->D8LK> zXD(XmXk*62Cuya&At?^3vA-8HeT<)R`(RM3v133cqb8hCeCxx&wG?RCA3xyFOHvl;p(alt|?61?+i_8@yG` z=T1Sgn^hLhW}r=(ZKJb{Ih0Z#lEoqWEEEt?hmqrkNB9SaaJR#uQHChUlzPk;+@lHL zH<0iveSGsZYvv_LTLDxsKgwn&XhEuC4r&}GxTOriazcp*8Vmj5at1v$rpyFIZ%O^? zL8CV+NryjXCLf`CWlD!K(|xG6rHf;Al+@EMpyd~>YDimSCpEA=$|XY;R143xsn9Cz zQxh_5Cml;uXSi$?=d|V3yNHSmlj1s;T<8%eEp>QZzMjDW7ro_dQHvB|Ia6m9myLoz zudA!|m)!x#BMv4n^9EfUc%wJObfd~!bJjOz8fQ1QTX?YMlg(aGMO2tJc-3Zt$KIed z^o(9P(PyQ_#`u9+&)juEZuAH2B-;SeLAxm{?`ro#2|+q0?raX@n=eD?5xhnk^BTfDe@bS_0oQq@OfT0j?PUdd-H!j+Sn%?hn+e0jWQ zZXHL5CI-yL^aARyS39=mV3nSFP}i8x1r()PwuoFkGd$FQFk7UA{r_bwcCWk!ws%4U2||H}oa)D4zH0gEG4%YGODQFcb2uf(W~^U-5dR|R^S zQv79^@~vk390ZDV8g!03$a*?MC`)PfY*KICXIF5-wAP~pDlyz3z{#?M_K8W@gG5y# zfxU@tE7HDzR4;Y89k}SXX|3Es3L?5{=^BcMeP>JNbP7~AB^}GtenkR|0V0xqCm~cR ze1VEVBH$WJ>VISr#x6(>T{TBOU))!UqCt+woJv%w!u<2s-Z-A$Rg*2fI@nF{8w-*; z==n5vRQ8owsP97JxQ@efXWV6MXScIwOi{flU~4fXROjJKjWRvWS3}>ptry(89=z0e z;EME|w9hW=k2fbAPqwk&KpzJl;=EjsXh_=Sh+GoK+zBDJVhomNVy7adc(s?Qt_m{x z>72M{q&kpwkRbO@yYl)oRNx5;cT%Go6`7r=P5Gfe)QsD|VX8W)w6KI~De$>!X28M5 z9dUF?vn;whyQjA$aCV#A5T9H&;^01i{1#X5kPpQ*I2*pu;q&V;q7&Hng`QN5`&+5xAL1`VaewlGgt&82>0+w=IuaMVlX zRKa<1D%_;T1^tF8|#kqlwlE?c0aEC$UH#(n59_9foqgFEgks}wNcPOBzya1O0Jd~PGI$C19Q@#!^4gku z{OH8(Vu9*N@@_I6)odIkfZF{)%Q4o{(Yh(;Oef^)edY_qSbm9oB0pz=lM$?1w)q8M zSF4L_>!n-fOGop*1VO#MV2>78TuWBC_eHnGRkO&peXQa=o-A1LYDn(`v9o72J{Cl* zarms0PD<)J%SiNVtfGLJ%YetrK|tXGdx*~gUwDi6I-p&-gw@*n_gC7^6MUBD$~$Cr z2AS%&XB}UfsiWs3oU?xv>kaa$ffs*Y`J>Zng)zd8oA(nXk;T(3E#)LeH7e_@>>x6y zMw;5+D>)6e_4{cLQ64KpSF7sXs-MhWti}07qDI*RP8$bh!!B5MH3%p%ABU>$Y)%EptU$hRnWvuZa5jkvJJY?cYW$K%;e*t{2TtR=gPgVtjE_#u3V%?n{NwDErl94(&!KL zu%&x5V*=j{e?oBwc_}l^(EXD4Wy;u~$R@B}uuJuk zg`aA<1J=Z$HXpUWT`H2!iyT++ z%z!(c9}3NP%o09T7?(9xCsqrV*t2RO+}LD%^NE)L8rLmpjC5Q%`Y{QK9h(DbseR}J z@$b;G1Dl>}W)F(qK|-p1Cq93gqrT%ge{J(jvgJxsfs{5zH}bZx0!;a3S016z3pgwr zTEOm4#gid6bjY7#v>L9ZqnqlxzQ5P}34HbYZA4!IoK3P#^@3`X;HCW#dCrCvHK@w} zu>KTqBU9Agw7y%21!y1Y{?ORE1*kJcl`7tWtg`oR~Q=e@NL>E82wF1HzKXohYu&iaS2hP4JIJhz)bRo*z| zo*HWV^KwJZbQ9bL_nX zMKV|6(_FY7UbM=Qi1MdVP6aT3>0Rcr9`EDarW=E;iWn70^(HJ(beQ)pNig+&@f-R% zF@abO>9iqYD3M-$wt5ZdpKid;QFicXw|@T+H3+9Zy;x)e32v1UA>&&>K*<%V9z#sS zx?P%`sNZNd7xi+U+L$}gbY6Rt6SZVlecdeUQMEPa`v7E}Ns&14P@AG^=91kcthK|r zZ#0!CjuI`++yjYa+D-d8Th@eF_t%MoHAJYV)mLH+qRL0T+-`v z6Vr6+{SXiGIDZ)PvcKU5)oGF9OL>9t-ij8q-3AGgpVI$CU!da1x6*c&k}&&7rtG|T zx04(Q@>!eBEowlot+A3DUh1n!=)wSl%h?hW&ej&YylttH16nkm=9z|W)(&wYl=Oqd z@E*ZEOyPg!6>)3X&C`XcYVxF~*fW3Pp`O)s#6O(pYl425?WWH^7;R`?W6nBfhFY`W zJD{!Y<2uvfBe)MC>p|jvJ%-A*G-2>_Hv!~%Ww~6ebzOZX!)0KSkCrC-!C?N*`6Qf(0CW6A}4bFy`6Swy{o$1Ff!zf&2^L_DU z^?fQy4Y%5$2=$fHWL^s~=Q>a9%IQGQs!cSUr`u=vH&U?V4_@@~jfynyz>s(M&FY{4 z7h=S`*+@b~a+#;i5E1=t)%pqzc^JPBw35|&eS7dj?*5d-#gdq~>{&C}@Km@wR|4*1 zck__QD@Oc9T7s#|KcOwh?+hLw*&%(Bs$e72Ht**{MOlt?6}m%xln_#(mggg!DYX-v zWF8cy-$jbq6&FO^KU@g8fy0g3^(X)+3453L3L@jRw|l80*75zZ@@Mc$wz%S(l|RWI zgWFN@g(`*Jq6!%s7R!cJ9_#c9`wUktV7CckxW8@f^7JMN$(4iPUluTc=6|}Fm{=77?~6s=7Y=!m zdasFC&uqtun=HNjTh82W;wvQ}mw%5KGq?Dq6B)7j zv`E7z+V5I#MePxJ+moS1p8qih?EitirW3z0v4Z)+2AoODIZWPVLh%u1GHcg{c6E^7 zgE|Io_r^bZ(mC=pF`%`DnYXhmis`w>nS7>kNhfhzu8g)6i z0nXEUkvfY&t@JKU)RKT5p}-1}?X4< z0wBn4r(iTHVhp7iu*%98fa9>9nVGd9DrUi4)f=s+MOo*Y2YO{50P1JhjacQ zD`T2d3-n%=pG@J4r`}wmJ3{oGowWo6VKc;eytgiJ3+!IL7Og=g{#UQj2C#7IZ4dk) zEXi!(a_c+0%ygq(+z;z;c|=k#Gk&F&Y+>;?sA)C$bgv5*^?kJPNIXcJH;;a>NLpIN zxrA`GrW}heWi6X}#OD?1d+69?K5Z8smzpO(_+zj^7j-%AX)Y6F)rMc)1P+e0Q3V;0 z1(qJk?;~enQQN#Gd2Y0Dq7v-5P$1MopoJHnJ{{Vf6QYJfrW=OW$iAADyq_?097Ckq zwjD^i{uZA2!IoH&`%xu(<(M{Agge5ZvdOX;5cKtz{`gQ0gmAiE3kqu59!{^0Wfs{W z<(5+FzkpdUZr%S?WO1&ORL-?DUYwsC`-W!tbi!CyHzETdG^!#=B=FcI}sLjs_kS4_{b8Xjn=2)(TL)0-z&F zklfI2`h-s$jTR7`E$C95%t>`R)5O9(w2J-E#@ixA%^IQA@(t+gKe{LB`F?1`{iD}= zQ-t^Z2QNM29?5b?V-16(dA}IT)hjfGQrK<#daj~EE3+kqOhG|MpR@8lBJ7(X1GC)P zxF5Sr6a2&?{6F{lZs?C*lhX48`K@jpYCXK(#w2X(S<4=9?%Qy6dUhraUf5q%GszCQ zGXR}oSJ2i|X5r;Ai#hTW?liCA&oexbJEehTZONSDb`$86E9~E_n$UE1#hwYUCz}Ko@Eg&lU&#^bJdR^ zt3-~hWxP5rsL^@9Qz#uH9tqL6%>W(Ff0|OVdAScN+qokh@q;I3G41-d)gTijC*Wv+ z73NXg=mlQq>`a30F|xgI@}#ciJoN+J0X<5_FFq2-A?gEMna zpQ$nPIA;`#ISG}cYq9HM)2mZ4)OkrA;YU*nD(TZz7hA=fDrGOh8zMU6c?dMiTgAZ) zOqE4C7`XOm=!+5(;_I@&PfQzy+-2*MNla+;ClvWpL+*Lm%-^o>fw793e=&{&&z;&Y zO$a1chVFjF`VD)p_~#Cm&&~iX1&u3)Sb@`pkVK_6eh>qnnhLURz~m}7AgRljch$rv zCZ4<2_$7m1Lm(;lpFL7FO~8C6041n~%Cn4Wul?lZaVKwm8uzyTX-mxe+@TjY^UE;kzy#)NB~3 zcK8J|PtZ(}Kh}qJv+@n6tU$eABbZ8Ka%tOo7x;m6WN|0$@|n+ z?|QoTyi}iQdvP(5OJ>O8joaB>3kwT&`6iF;EdA5&a+7ZO$BU#n1K{L`5XtEmb3roe z6PXf<@9MXQ7w(J|!9jw)>2i$g7TNs~9PtnS#mGIvMr3UqBf2;cC-pI(v z0KcU}i4PuSKtHbU>J(9u{8T6ANmbrCyDgEO4NvLb5OkPxn@X#{rZjG`nx>X60zhGI zH|!_#*l?NZ=DjwD?Oj|?vV{mBQukM5zyn?V26cTehcbjNpIg7sU=ZGtU|%+>a`HRA zb|bmM+&V<}i(=f?aOv^7M?>dZvIr_#r7**0hg$E_q5kBGq8;9?KyWtIs#o>-H~Xo2 z^pVU_$eoAXC*d5={PN$`+jo(Bxy-eifc3Kc{7#t^nM^Onu)X*h51fXKl18dHauxQ# zwlDsfqxXt+?bW)+DnQ8Paw6yMyv$1);rl^poXB9-S%h<-f1CeR5K6clk?bcuv|uHF z;f1O3RBm+Lu2S>fnA#s7^FT-1knSz>VWOaM8mCQ%1TVaJl^yksD|p&?2@89ZQvKda z%s0S5jtfq&GK94#^z8{~cHzWnZc^wstMEF;N%HEa%=H3REBSuN2mOjOCn z>Ugn9xoLxex$?0alI=+>5_+mS;U2c5&^GSxtU#EL#_&nY+q7jg# z1TQ6(PiM0`>Mva-;|prMf6qtnVfw8XXfW1&BT|x#s0x;Wg)7)|&nI4yG@wWN9@RW_ z6@_q(9{RtXPWJfz?X{($!!;PThrHx@v*kh)8XDh z^Tt<7)ejgI6E=l>-?{SK8O8mmFfyU!%6q9c(P-kcIodEvQ|CPmihZJySzn%(q_W8Y zAkp?u0I^DVseCDU_kk*}(7bh~-X;6g{c_X>F+1UOZsvBgqVuI&+J#mQS*6_PXRVC|$SZGZ>9Si(o8I7nF8Cm#>H~aGr%FNGgw>2|<{SWEv?QC&pFlA7x2;9E~i! zjD@{QO{E`Rlh376h-oFG7IM*)F*w>F=Xwr7nN~Zu>dV(dzdwAz*-7e^S=q)1-w7J2 z4&4M$XBr{&qq$HOj>>iI0EKc*$a2eb)%tVnPTKduwL^#j!Flpqk46^FD+{d{_RGSO zfAVx{lkl2dw>LFz&-s&i|+#rNA}nxLAuo%>D78mipCON z=|$&EP-of-UB8Z+72{yYN+03^s9eHWKR#VL9efMj=v68v3)F5kt#wN{b>A#RXk>wK zAF)<_gFloGC-zr)P0L&~FXFPrHv?8qbTrWU0p@3oqIWY2R5KMj9SzcWi-s=sczw2} z9NWenB$RVQ!ldHT{Ne6X{8%F+viFJZHY{UW1oyBy2egQz_u@e+`5_M9DD;_b{F2<) zo&%=`7`E>#%l6b2EVs}gQ|zfE*gx`M3L;$Ods`f8uPbmB4@K$gpSI7nTn`dw#xH7U z>QD^YX{u+48(kH*aJV}pw0q|&3Df3t>@LbUz{x>0c#Wya0ZS>{KJpl zrAwEcWUCV-$Up@rtU5}i_Zmx>Hq|8Ri|?x2I^ zUhJks6BNiK9U$7tYQ>*_;fYGqQ=ta@EJoMptx5-)2m^{A!qf%M_A_}6cseonNQLlb#;&Wj+i4N9x znJG?6Mly*|;%uV+FhPMj_uQmb{z+C#AomRXf^%u48MmB&_2JLzR1I!tj8C)Xou$q7 z@-qF8`>{krL{gM|sXhvjIorn~Hk*C$!ESB096w$KseHq|(rGECg0T1>6{;q@C?@r2 zg=bK6@2jJ;;n~HNH$hWB{c6iXBU4tmxJ`&qB7LG%B5=mAbz{7y%T6Kc_|Zv>cj^9L zF}l8D1?V`v%XsAS7$h9d_xW4lYJm`5L;x-MWGi@FyUmu9M=wRiUNu^!osySIKb2WFZR6VnFkmPXi$}9h0 zw{nz8NbVl~{l9?U(f8kir|O@DU7S6V3J5TVNU2|2n*gGz>DF`)lv{koXCY!79%l!{ zx#o_uvACsvh(8nTcI{DDxtv zW2ot7m$qqqkA9I#fz4a;_eo?g7T+=x0BMWeu@}Ukdp{|Xue|wK^M+>o!s`3XvRYjt z#906NFoIEZyk*VhQCE%P=5#750amCWlsqx#P1mDlh=uhr|1+j*lDPjNyHKUX{i}E+ z-5;09N~ymv3N}wK*pr`KR?kQNU>ESf=WzNY{hZ%{?7tHsx-UGr&#GeLzfrs zgA)Tq4R64CTy+kFN85k?-FZRY>c|rVuQ$cjrUUlbQjMkZxEYJhlgnE=65APFaE69G zOn{Jise<{eIA_hrKbp-dD@U!M5~mXJg8PEpfK$*Rk2x%Nw9H6AIF*8Jjn(ude}BspCNjhm*zCPNUQ^%!&O99tEAT8hBE7g!OMD88 zWajW#_T=MOm8ri5w&{`Gl^d}{W{3?+u`5P@xQU+Uvreq=yfu19TK3%6QN%^dKj}2d z*iiVQ!En_dY0#~aK2hSsrXZNem!VWS+Y&lmtNXSuSt42Gwgn|)nU#j-zVT82P$HvX z876-{R*x%#ssDOG!o(qy2(h+~upj2Lb@toUuZ|W%p7=~rN1kh zzd*HUX>}`AXUiFEk$~H}l<|3t+d=#9ZSFPe#9rb``l_|-vAMI!d{^n$A*WtX ziP6!SH{J?U0=eGM0Pjl>&tNVvN`URemfd~qdGe0e9b5_opH~w-VU=ZPu@I1Gm@|zV zhUX^2<4k9K%zL`Jy3W}Ge^44olzko^iJGYgYHbkc>p`4~a3g1rRFxO?UA;!w8wA@xL z$vG3KF7J0q&#L`kaSlCOBF(*Bmh&!!CGjs&-#X#7vjBsgDBkh5!WS*s*dY{bs%-Ss znw7JHT8B#F5KVC1D$*kuzm|&9-qFnoNHG%uno;929y;M@ zrBf)UQf^1(JdQgFncIxH<`U(S&2?QECTb&dnOw^)oysk9*9=QFx*TJt97!mb&27Vk z){x7?Wo&HctH=4}`~&?3pU30#c|1Pv*X8kiPj5sn_!z2zMKjpJ))@rM3Ej039-CYHP2^>%YI34|6|N@Xo;CX+8NdR1kJcH zv#w|c9w6l$rw1O2Xg!xPDsM7X#3W<-DzNb3S-(*TNJ98-oZ>hklnQ0^mLsW^(WCzR zt%IuK&_ICS_5jRCS`Wfk1F#Te2L`M?K{wgbjB;fTYqNjZ^dA=pacEl zN_W7zmw=lR2qUCZqea5;e}j8&Rx+xR5X?Y`Krbjk6&5;4w#UlLRhw|MV6A%2aJ$6laHN)#LOCR zc6f&zI6WWqrL)qu*4_2xExqkGipyiWkHR13B^0iIb0MfaPT5d9e%{VEz0Yg)?fog?nF0{?T!A5)hnQlC_>ymF>%jV9V?o zq%EICE;02|hORumXGN6nK^URtuiNsJ&D`%8;k97*?In_@{%TCz+Me-lj7uFUU*dcl zY4igQ`EDEm)$h5#=FF@fDGKx3lxEYqw6Dlk93=7uH`rmZ!X~`xrKZE%4%tUlo@5HT z#j9!U0Ef65?cxzT0Zb5jh0M}5%^rJgFn?|l{0qqqmTWvR{4I$?34%a$m5dBpG~E9=dxKC zpr7$^%hairFG#IpnPhZbo=?10G9S5qzPB{hx{#vLrIBjN!?d;mCFehPgHp@@Mn*`w zsvjwfFm}~`qp?)Oma>JQ}3L7FDhH zYU$jNV3(e=w0pH3KJIiH_4VHJ)O(M^=nqa_zpCIchGVrdD`e4yi~9`l-2TNhsgu5- z@`Fc#!g$GF_LSx&gB2jt|0zN5|8{b*vUC5#jjjW@ktVJ$X@|TCom4sggWD;^ggMIq zkPy{oTFv$ju)@l-DvLXy^H`xJ_#|=%n@S1s(nHDx!oD%4e^RIYGnR=x7XgnU5=Fhc h#Ra1Ky*ma-T&z*&^uVBb*iM))=3wJu{nRQT>0c+yy%PWc literal 0 HcmV?d00001 diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index 63a69f746..3990034d2 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -13,8 +13,9 @@ import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; import org.commonmark.renderer.markdown.MarkdownRenderer; +import java.util.HashMap; +import java.util.Locale; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -34,7 +35,7 @@ public class AlertsExtension implements Parser.ParserExtension, HtmlRenderer.Htm private final Map customTypes; private AlertsExtension(Builder builder) { - this.customTypes = new LinkedHashMap<>(builder.customTypes); + this.customTypes = new HashMap<>(builder.customTypes); } public static Extension create() { @@ -47,7 +48,7 @@ public static Builder builder() { @Override public void extend(Parser.Builder parserBuilder) { - Set allowedTypes = new HashSet<>(STANDARD_TYPES); + var allowedTypes = new HashSet<>(STANDARD_TYPES); allowedTypes.addAll(customTypes.keySet()); parserBuilder.postProcessor(new AlertPostProcessor(allowedTypes)); } @@ -81,7 +82,7 @@ public Set getSpecialCharacters() { * Builder for configuring the alerts extension. */ public static class Builder { - private final Map customTypes = new LinkedHashMap<>(); + private final Map customTypes = new HashMap<>(); /** * Adds a custom alert type with a display title. @@ -100,7 +101,7 @@ public Builder addCustomType(String type, String title) { if (title == null || title.isEmpty()) { throw new IllegalArgumentException("Title must not be null or empty"); } - if (!type.equals(type.toUpperCase())) { + if (!type.equals(type.toUpperCase(Locale.ROOT))) { throw new IllegalArgumentException("Type must be uppercase: " + type); } customTypes.put(type, title); diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java index 202c20290..763b2e217 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -22,11 +22,11 @@ public AlertHtmlNodeRenderer(HtmlNodeRendererContext context, Map attributes = new LinkedHashMap<>(); + var attributes = new LinkedHashMap(); attributes.put("class", "markdown-alert markdown-alert-" + cssClass); attributes.put("data-alert-type", cssClass); @@ -47,8 +47,9 @@ protected void renderAlert(Alert alert) { } private String getAlertTitle(String type) { - if (customTypeTitles.containsKey(type)) { - return customTypeTitles.get(type); + var customTypeTitle = customTypeTitles.get(type); + if (customTypeTitle != null) { + return customTypeTitle; } switch (type) { case "NOTE": @@ -67,9 +68,9 @@ private String getAlertTitle(String type) { } private void renderChildren(Node parent) { - Node node = parent.getFirstChild(); + var node = parent.getFirstChild(); while (node != null) { - Node next = node.getNext(); + var next = node.getNext(); context.render(node); node = next; } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java index edc988c94..e3da62aea 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java @@ -28,9 +28,9 @@ protected void renderAlert(Alert alert) { } private void renderChildren(Node parent) { - Node node = parent.getFirstChild(); + var node = parent.getFirstChild(); while (node != null) { - Node next = node.getNext(); + var next = node.getNext(); context.render(node); node = next; } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java index dcb1a25eb..45b34bb46 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java @@ -1,6 +1,7 @@ package org.commonmark.ext.gfm.alerts.internal; import org.commonmark.ext.gfm.alerts.Alert; +import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; import java.util.Set; @@ -8,13 +9,13 @@ public abstract class AlertNodeRenderer implements NodeRenderer { @Override - public Set> getNodeTypes() { + public Set> getNodeTypes() { return Set.of(Alert.class); } @Override - public void render(org.commonmark.node.Node node) { - Alert alert = (Alert) node; + public void render(Node node) { + var alert = (Alert) node; renderAlert(alert); } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java index 0d02c3527..b524adc05 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java @@ -1,16 +1,21 @@ package org.commonmark.ext.gfm.alerts.internal; import org.commonmark.ext.gfm.alerts.Alert; -import org.commonmark.node.*; +import org.commonmark.node.BlockQuote; +import org.commonmark.node.HardLineBreak; +import org.commonmark.node.Node; +import org.commonmark.node.Paragraph; +import org.commonmark.node.SoftLineBreak; +import org.commonmark.node.Text; import org.commonmark.parser.PostProcessor; +import java.util.Locale; import java.util.Set; -import java.util.regex.Matcher; import java.util.regex.Pattern; public class AlertPostProcessor implements PostProcessor { - // Case-insensitive matching for alert type (GitHub supports any case) + // Alert type marker, matching any case (GitHub supports lowercase, mixed, and uppercase) private static final Pattern ALERT_PATTERN = Pattern.compile("^\\[!([a-zA-Z]+)]\\s*$"); private final Set allowedTypes; @@ -21,150 +26,85 @@ public AlertPostProcessor(Set allowedTypes) { @Override public Node process(Node document) { - AlertVisitor visitor = new AlertVisitor(allowedTypes); - document.accept(visitor); + // Only look at direct children of Document — GitHub only detects alerts at the top level. + var child = document.getFirstChild(); + while (child != null) { + var next = child.getNext(); + if (child instanceof BlockQuote) { + tryConvertToAlert((BlockQuote) child); + } + child = next; + } return document; } - private static class AlertVisitor extends AbstractVisitor { - - private final Set allowedTypes; - - AlertVisitor(Set allowedTypes) { - this.allowedTypes = allowedTypes; + private void tryConvertToAlert(BlockQuote blockQuote) { + var firstChild = blockQuote.getFirstChild(); + if (!(firstChild instanceof Paragraph)) { + return; } - @Override - public void visit(BlockQuote blockQuote) { - // Only convert top-level block quotes (direct children of Document). - // This matches GitHub's behavior where alerts are only detected at the document level. - if (blockQuote.getParent() instanceof Document) { - if (tryConvertToAlert(blockQuote)) { - return; - } - } - visitChildren(blockQuote); + var paragraph = (Paragraph) firstChild; + var firstInline = paragraph.getFirstChild(); + if (!(firstInline instanceof Text)) { + return; } - private boolean tryConvertToAlert(BlockQuote blockQuote) { - Node firstChild = blockQuote.getFirstChild(); - if (!(firstChild instanceof Paragraph)) { - return false; - } - - Paragraph paragraph = (Paragraph) firstChild; - Node firstInline = paragraph.getFirstChild(); - if (!(firstInline instanceof Text)) { - return false; - } + var textNode = (Text) firstInline; - Text textNode = (Text) firstInline; - String literal = textNode.getLiteral(); - - // The alert marker can be the entire text node content, or just the first line - // before a line break. We need to check both cases. - // Trailing spaces on the marker line create a HardLineBreak instead of SoftLineBreak. - String markerText; - Node afterMarker = firstInline.getNext(); - if (afterMarker instanceof SoftLineBreak || afterMarker instanceof HardLineBreak || afterMarker == null) { - markerText = literal; - } else { - // Text followed by something other than a line break on same line - not an alert - return false; - } - - Matcher matcher = ALERT_PATTERN.matcher(markerText); - if (!matcher.matches()) { - return false; - } - - String type = matcher.group(1).toUpperCase(); - if (!allowedTypes.contains(type)) { - return false; - } + // The alert marker can be the entire text node content, or just the first line + // before a line break (trailing spaces create a HardLineBreak instead of SoftLineBreak). + var afterMarker = firstInline.getNext(); + if (afterMarker != null && !(afterMarker instanceof SoftLineBreak) && !(afterMarker instanceof HardLineBreak)) { + // Text followed by something other than a line break - not an alert + return; + } - // Must have content after the marker line. An alert with ONLY the marker - // and no content is a normal blockquote on GitHub. - boolean hasContentAfterMarker; - if (afterMarker instanceof SoftLineBreak || afterMarker instanceof HardLineBreak) { - // There's a line break after marker - check if there's content after it - hasContentAfterMarker = afterMarker.getNext() != null || paragraph.getNext() != null; - } else { - // Marker is the only thing in this text node - hasContentAfterMarker = paragraph.getNext() != null; - } + var matcher = ALERT_PATTERN.matcher(textNode.getLiteral()); + if (!matcher.matches()) { + return; + } - if (!hasContentAfterMarker) { - return false; - } + var type = matcher.group(1).toUpperCase(Locale.ROOT); + if (!allowedTypes.contains(type)) { + return; + } - // Check if the content after the marker is only whitespace - if (isContentWhitespaceOnly(paragraph, firstInline)) { - return false; + // Must have content after the marker line. An alert with ONLY the marker + // and no content is a normal blockquote on GitHub. + if (afterMarker != null) { + // There's a line break after marker - check if there's content after it + if (afterMarker.getNext() == null && paragraph.getNext() == null) { + return; } - - // Valid alert. Create Alert node and transfer children. - Alert alert = new Alert(type); - - blockQuote.insertAfter(alert); - - // Remove the marker text and line break from the first paragraph - if (afterMarker instanceof SoftLineBreak || afterMarker instanceof HardLineBreak) { - afterMarker.unlink(); + afterMarker.unlink(); + } else { + // Marker is the only thing in the paragraph + if (paragraph.getNext() == null) { + return; } - firstInline.unlink(); + } - // If paragraph is now empty, remove it - if (paragraph.getFirstChild() == null) { - paragraph.unlink(); - } + // Valid alert. Create Alert node and transfer children. + var alert = new Alert(type); + blockQuote.insertAfter(alert); - // Move remaining children from blockquote to alert - Node child = blockQuote.getFirstChild(); - while (child != null) { - Node next = child.getNext(); - alert.appendChild(child); - child = next; - } + // Remove the marker text from the first paragraph + firstInline.unlink(); - blockQuote.unlink(); - return true; + // If paragraph is now empty, remove it + if (paragraph.getFirstChild() == null) { + paragraph.unlink(); } - private boolean isContentWhitespaceOnly(Paragraph firstParagraph, Node markerNode) { - // Check inline nodes after the marker in the first paragraph - Node next = markerNode.getNext(); - while (next != null) { - if (next instanceof Text) { - if (!((Text) next).getLiteral().trim().isEmpty()) { - return false; - } - } else if (!(next instanceof SoftLineBreak) && !(next instanceof HardLineBreak)) { - return false; - } - next = next.getNext(); - } - - // Check block-level siblings after the first paragraph - Node block = firstParagraph.getNext(); - while (block != null) { - if (block instanceof Paragraph) { - Node child = block.getFirstChild(); - while (child != null) { - if (child instanceof Text && !((Text) child).getLiteral().trim().isEmpty()) { - return false; - } else if (!(child instanceof Text) && !(child instanceof SoftLineBreak) && !(child instanceof HardLineBreak)) { - return false; - } - child = child.getNext(); - } - } else { - return false; - } - block = block.getNext(); - } - - return true; + // Move remaining children from blockquote to alert + var child = blockQuote.getFirstChild(); + while (child != null) { + var next = child.getNext(); + alert.appendChild(child); + child = next; } + + blockQuote.unlink(); } } diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java index dba1628cb..34b78385c 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java @@ -1,6 +1,5 @@ package org.commonmark.ext.gfm.alerts.examples; -import org.commonmark.Extension; import org.commonmark.ext.gfm.alerts.AlertsExtension; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; @@ -22,19 +21,17 @@ private static void standardTypesExample() { System.out.println("STANDARD GFM ALERT TYPES"); System.out.println("=".repeat(60)); - // Create the alerts extension with default settings - Extension extension = AlertsExtension.create(); + var extension = AlertsExtension.create(); - Parser parser = Parser.builder() + var parser = Parser.builder() .extensions(List.of(extension)) .build(); - HtmlRenderer renderer = HtmlRenderer.builder() + var renderer = HtmlRenderer.builder() .extensions(List.of(extension)) .build(); - // Example markdown with all standard alert types - String markdown = "# GFM Alerts Demo\n\n" + + var markdown = "# GFM Alerts Demo\n\n" + "> [!NOTE]\n" + "> Highlights information that users should take into account.\n\n" + "> [!TIP]\n" + @@ -46,7 +43,7 @@ private static void standardTypesExample() { "> [!CAUTION]\n" + "> Advises about risks or negative outcomes.\n"; - String html = renderer.render(parser.parse(markdown)); + var html = renderer.render(parser.parse(markdown)); System.out.println("Markdown Input:"); System.out.println(markdown); @@ -58,33 +55,27 @@ private static void customTypesExample() { System.out.println("CUSTOM ALERT TYPES"); System.out.println("=".repeat(60)); - // Create extension with custom types - Extension extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") - .addCustomType("SUCCESS", "Success") - .addCustomType("DANGER", "Danger") + var extension = AlertsExtension.builder() + .addCustomType("BUG", "Known Bug") .build(); - Parser parser = Parser.builder() + var parser = Parser.builder() .extensions(List.of(extension)) .build(); - HtmlRenderer renderer = HtmlRenderer.builder() + var renderer = HtmlRenderer.builder() .extensions(List.of(extension)) .build(); - // Example markdown with custom alert types - String markdown = "# Custom Alert Types\n\n" + - "> [!INFO]\n" + - "> This is a custom information alert.\n\n" + - "> [!SUCCESS]\n" + - "> Operation completed successfully!\n\n" + - "> [!DANGER]\n" + - "> This action is dangerous and irreversible.\n\n" + + var markdown = "# Custom Alert Types\n\n" + "> [!NOTE]\n" + - "> Standard types still work alongside custom types.\n"; + "> Useful information that users should know.\n\n" + + "> [!TIP]\n" + + "> Helpful advice for doing things better.\n\n" + + "> [!BUG]\n" + + "> This feature has a known issue with large files (see #42).\n"; - String html = renderer.render(parser.parse(markdown)); + var html = renderer.render(parser.parse(markdown)); System.out.println("Markdown Input:"); System.out.println(markdown); From f8f7684fb67d92d679ff87da4a32e89c4588fdd3 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Mon, 30 Mar 2026 19:24:09 +1100 Subject: [PATCH 252/345] add node depth limit --- .../org/commonmark/node/AbstractVisitor.java | 23 ++++++- .../commonmark/test/AbstractVisitorTest.java | 64 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java index 7edd635d7..b7389ceb9 100644 --- a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java +++ b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java @@ -7,6 +7,19 @@ * call {@link #visitChildren}. */ public abstract class AbstractVisitor implements Visitor { + private final int maxDepth; + private int currentDepth; + + public AbstractVisitor() { + this(Integer.MAX_VALUE); + } + + protected AbstractVisitor(int maxDepth) { + if (maxDepth < 0) { + throw new IllegalArgumentException("maxDepth must be >= 0"); + } + this.maxDepth = maxDepth; + } @Override public void visit(BlockQuote blockQuote) { @@ -129,12 +142,20 @@ public void visit(CustomNode customNode) { * @param parent the parent node whose children should be visited */ protected void visitChildren(Node parent) { + if (currentDepth >= maxDepth) { + return; + } Node node = parent.getFirstChild(); while (node != null) { // A subclass of this visitor might modify the node, resulting in getNext returning a different node or no // node after visiting it. So get the next node before visiting. Node next = node.getNext(); - node.accept(this); + currentDepth++; + try { + node.accept(this); + } finally { + currentDepth--; + } node = next; } } diff --git a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java index edb6936f4..554f9c25e 100644 --- a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java @@ -3,9 +3,38 @@ import org.commonmark.node.*; import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class AbstractVisitorTest { + @Test + public void maxDepthMustBeZeroOrGreater() { + assertThatThrownBy(() -> new RecordingVisitor(-1)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void maxDepthZeroVisitsOnlyRoot() { + var paragraph = paragraphTree(); + var visitor = new RecordingVisitor(0); + + paragraph.accept(visitor); + + assertThat(visitor.visited).containsExactly("paragraph"); + } + + @Test + public void maxDepthOneVisitsDirectChildrenButNotGrandchildren() { + var paragraph = paragraphTree(); + var visitor = new RecordingVisitor(1); + + paragraph.accept(visitor); + + assertThat(visitor.visited).containsExactly("paragraph", "emphasis", "text:tail"); + } @Test public void replacingNodeInVisitorShouldNotDestroyVisitOrder() { @@ -34,4 +63,39 @@ private static void assertCode(String expectedLiteral, Node node) { Code code = (Code) node; assertThat(code.getLiteral()).isEqualTo(expectedLiteral); } + + private static Paragraph paragraphTree() { + var paragraph = new Paragraph(); + var emphasis = new Emphasis(); + emphasis.appendChild(new Text("nested")); + paragraph.appendChild(emphasis); + paragraph.appendChild(new Text("tail")); + return paragraph; + } + + private static final class RecordingVisitor extends AbstractVisitor { + private final List visited = new ArrayList<>(); + + private RecordingVisitor(int maxDepth) { + super(maxDepth); + } + + @Override + public void visit(Paragraph paragraph) { + visited.add("paragraph"); + super.visit(paragraph); + } + + @Override + public void visit(Emphasis emphasis) { + visited.add("emphasis"); + super.visit(emphasis); + } + + @Override + public void visit(Text text) { + visited.add("text:" + text.getLiteral()); + super.visit(text); + } + } } From 50d2c368f8f4225da809697f371c7c4ea9ee05c2 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Mon, 30 Mar 2026 21:21:55 +1100 Subject: [PATCH 253/345] abort deep nodes early --- .../commonmark/internal/DocumentParser.java | 8 +- .../java/org/commonmark/parser/Parser.java | 26 ++++- .../java/org/commonmark/test/ParserTest.java | 102 ++++++++++++++++++ 3 files changed, 134 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index d935f8d27..2b9db45cc 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -76,6 +76,7 @@ public class DocumentParser implements ParserState { private final List linkProcessors; private final Set linkMarkers; private final IncludeSourceSpans includeSourceSpans; + private final int maxOpenBlockParsers; private final DocumentBlockParser documentBlockParser; private final Definitions definitions = new Definitions(); @@ -84,7 +85,8 @@ public class DocumentParser implements ParserState { public DocumentParser(List blockParserFactories, InlineParserFactory inlineParserFactory, List inlineContentParserFactories, List delimiterProcessors, - List linkProcessors, Set linkMarkers, IncludeSourceSpans includeSourceSpans) { + List linkProcessors, Set linkMarkers, + IncludeSourceSpans includeSourceSpans, int maxOpenBlockParsers) { this.blockParserFactories = blockParserFactories; this.inlineParserFactory = inlineParserFactory; this.inlineContentParserFactories = inlineContentParserFactories; @@ -92,6 +94,7 @@ public DocumentParser(List blockParserFactories, InlineParse this.linkProcessors = linkProcessors; this.linkMarkers = linkMarkers; this.includeSourceSpans = includeSourceSpans; + this.maxOpenBlockParsers = maxOpenBlockParsers; this.documentBlockParser = new DocumentBlockParser(); activateBlockParser(new OpenBlockParser(documentBlockParser, 0)); @@ -461,6 +464,9 @@ private void addSourceSpans() { } private BlockStartImpl findBlockStart(BlockParser blockParser) { + if (openBlockParsers.size() - 1 >= maxOpenBlockParsers) { + return null; + } MatchedBlockParser matchedBlockParser = new MatchedBlockParserImpl(blockParser); for (BlockParserFactory blockParserFactory : blockParserFactories) { BlockStart result = blockParserFactory.tryStart(this, matchedBlockParser); diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index b98d0581f..9e90e906b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -37,6 +37,7 @@ public class Parser { private final InlineParserFactory inlineParserFactory; private final List postProcessors; private final IncludeSourceSpans includeSourceSpans; + private final int maxOpenBlockParsers; private Parser(Builder builder) { this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes); @@ -47,6 +48,7 @@ private Parser(Builder builder) { this.linkProcessors = builder.linkProcessors; this.linkMarkers = builder.linkMarkers; this.includeSourceSpans = builder.includeSourceSpans; + this.maxOpenBlockParsers = builder.maxOpenBlockParsers; // Try to construct an inline parser. Invalid configuration might result in an exception, which we want to // detect as soon as possible. @@ -106,7 +108,7 @@ public Node parseReader(Reader input) throws IOException { private DocumentParser createDocumentParser() { return new DocumentParser(blockParserFactories, inlineParserFactory, inlineContentParserFactories, - delimiterProcessors, linkProcessors, linkMarkers, includeSourceSpans); + delimiterProcessors, linkProcessors, linkMarkers, includeSourceSpans, maxOpenBlockParsers); } private Node postProcess(Node document) { @@ -129,6 +131,7 @@ public static class Builder { private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); private InlineParserFactory inlineParserFactory; private IncludeSourceSpans includeSourceSpans = IncludeSourceSpans.NONE; + private int maxOpenBlockParsers = Integer.MAX_VALUE; /** * @return the configured {@link Parser} @@ -200,6 +203,27 @@ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { return this; } + /** + * Limit how many non-document block parsers may be open at once while parsing. + *

      + * Once the limit is reached, additional block starts are treated as plain text instead of + * creating deeper nested block structure. + *

      + * The document root parser is not counted. The default is unlimited, so callers that keep + * using {@code Parser.builder().build()} preserve current behavior. + * + * @param maxOpenBlockParsers maximum number of open non-document block parsers, must be + * zero or greater + * @return {@code this} + */ + public Builder maxOpenBlockParsers(int maxOpenBlockParsers) { + if (maxOpenBlockParsers < 0) { + throw new IllegalArgumentException("maxOpenBlockParsers must be >= 0"); + } + this.maxOpenBlockParsers = maxOpenBlockParsers; + return this; + } + /** * Add a custom block parser factory. *

      diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index c119b5e2d..6164759ec 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -4,6 +4,7 @@ import org.commonmark.parser.*; import org.commonmark.parser.block.*; import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.testutil.TestResources; import org.junit.jupiter.api.Test; @@ -135,6 +136,76 @@ public void threading() throws Exception { } } + @Test + public void maxOpenBlockParsersMustBeZeroOrGreater() { + assertThatThrownBy(() -> + Parser.builder().maxOpenBlockParsers(-1)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void maxOpenBlockParsersIsOptIn() { + var parser = Parser.builder().build(); + + var document = parser.parse(alternatingNestedList(9)); + + assertThat(renderText(deepestStructuredParagraph(document, 9))).isEqualTo("level9"); + } + + @Test + public void maxOpenBlockParsersPreservesSevenLogicalListLevelsAtSeventeenBlocks() { + var parser = Parser.builder().maxOpenBlockParsers(17).build(); + + var document = parser.parse(alternatingNestedList(7)); + + assertThat(renderText(deepestStructuredParagraph(document, 7))).isEqualTo("level7"); + } + + @Test + public void maxOpenBlockParsersPreservesEightLogicalListLevelsAtSeventeenBlocks() { + var parser = Parser.builder().maxOpenBlockParsers(17).build(); + + var document = parser.parse(alternatingNestedList(8)); + + assertThat(renderText(deepestStructuredParagraph(document, 8))).isEqualTo("level8"); + } + + @Test + public void maxOpenBlockParsersDegradesTheNinthLogicalListLevelToPlainText() { + var parser = Parser.builder().maxOpenBlockParsers(17).build(); + + var document = parser.parse(alternatingNestedList(9)); + var deepestParagraph = deepestStructuredParagraph(document, 8); + + assertThat(renderText(deepestParagraph)).isEqualTo("level8\n- level9"); + assertThat(deepestParagraph.getNext()).isNull(); + } + + @Test + public void maxOpenBlockParsersAlsoLimitsMixedListAndBlockQuoteNesting() { + var parser = Parser.builder().maxOpenBlockParsers(5).build(); + + var document = parser.parse(String.join("\n", + "- level1", + " > level2", + " > > level3", + " > > > level4")); + + var listBlock = document.getFirstChild(); + assertThat(listBlock).isInstanceOf(BulletList.class); + + var listItem = listBlock.getFirstChild(); + var blockQuote1 = listItem.getLastChild(); + assertThat(blockQuote1).isInstanceOf(BlockQuote.class); + + var blockQuote2 = blockQuote1.getLastChild(); + assertThat(blockQuote2).isInstanceOf(BlockQuote.class); + + var deepestParagraph = blockQuote2.getLastChild(); + assertThat(deepestParagraph).isInstanceOf(Paragraph.class); + assertThat(renderText(deepestParagraph)).isEqualTo("level3\n> level4"); + assertThat(deepestParagraph.getNext()).isNull(); + } + private String firstText(Node n) { while (!(n instanceof Text)) { assertThat(n).isNotNull(); @@ -142,4 +213,35 @@ private String firstText(Node n) { } return ((Text) n).getLiteral(); } + + private Paragraph deepestStructuredParagraph(Node document, int levels) { + Node node = document.getFirstChild(); + for (int level = 1; level <= levels; level++) { + assertThat(node).isInstanceOf(ListBlock.class); + var listItem = node.getFirstChild(); + assertThat(listItem).isNotNull(); + if (level == levels) { + assertThat(listItem.getFirstChild()).isInstanceOf(Paragraph.class); + return (Paragraph) listItem.getFirstChild(); + } + node = listItem.getLastChild(); + } + throw new AssertionError("unreachable"); + } + + private String renderText(Node node) { + return TextContentRenderer.builder().build().render(node).trim(); + } + + private String alternatingNestedList(int levels) { + int indent = 0; + var lines = new ArrayList(); + for (int level = 1; level <= levels; level++) { + var ordered = level % 2 == 0; + var marker = ordered ? "1. " : "- "; + lines.add(" ".repeat(indent) + marker + "level" + level); + indent += marker.length(); + } + return String.join("\n", lines); + } } From 7e39836541e97eec661dd2e331b19b45082cbc19 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Mon, 30 Mar 2026 21:23:25 +1100 Subject: [PATCH 254/345] Apply suggestion from @spirit-at-canva --- .../src/main/java/org/commonmark/internal/DocumentParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 2b9db45cc..07d97296b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -464,7 +464,7 @@ private void addSourceSpans() { } private BlockStartImpl findBlockStart(BlockParser blockParser) { - if (openBlockParsers.size() - 1 >= maxOpenBlockParsers) { + if (openBlockParsers.size() > maxOpenBlockParsers) { return null; } MatchedBlockParser matchedBlockParser = new MatchedBlockParserImpl(blockParser); From 7a9558b095574202647fb2e7cb061dfbe082e396 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Mon, 30 Mar 2026 21:25:11 +1100 Subject: [PATCH 255/345] Apply suggestion from @spirit-at-canva --- commonmark/src/main/java/org/commonmark/parser/Parser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 9e90e906b..dc4decf28 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -210,7 +210,7 @@ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { * creating deeper nested block structure. *

      * The document root parser is not counted. The default is unlimited, so callers that keep - * using {@code Parser.builder().build()} preserve current behavior. + * using {@code Parser.builder().build()} preserve behavior. * * @param maxOpenBlockParsers maximum number of open non-document block parsers, must be * zero or greater From 0c026d5b0062c4edf71d592dce67796c89628ed1 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 12:59:26 +1100 Subject: [PATCH 256/345] remove changes to visitor --- .../org/commonmark/node/AbstractVisitor.java | 23 +--- .../commonmark/test/AbstractVisitorTest.java | 64 ----------- .../java/org/commonmark/test/ParserTest.java | 105 ++++++++++++++++++ 3 files changed, 106 insertions(+), 86 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java index b7389ceb9..7edd635d7 100644 --- a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java +++ b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java @@ -7,19 +7,6 @@ * call {@link #visitChildren}. */ public abstract class AbstractVisitor implements Visitor { - private final int maxDepth; - private int currentDepth; - - public AbstractVisitor() { - this(Integer.MAX_VALUE); - } - - protected AbstractVisitor(int maxDepth) { - if (maxDepth < 0) { - throw new IllegalArgumentException("maxDepth must be >= 0"); - } - this.maxDepth = maxDepth; - } @Override public void visit(BlockQuote blockQuote) { @@ -142,20 +129,12 @@ public void visit(CustomNode customNode) { * @param parent the parent node whose children should be visited */ protected void visitChildren(Node parent) { - if (currentDepth >= maxDepth) { - return; - } Node node = parent.getFirstChild(); while (node != null) { // A subclass of this visitor might modify the node, resulting in getNext returning a different node or no // node after visiting it. So get the next node before visiting. Node next = node.getNext(); - currentDepth++; - try { - node.accept(this); - } finally { - currentDepth--; - } + node.accept(this); node = next; } } diff --git a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java index 554f9c25e..edb6936f4 100644 --- a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java @@ -3,38 +3,9 @@ import org.commonmark.node.*; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; public class AbstractVisitorTest { - @Test - public void maxDepthMustBeZeroOrGreater() { - assertThatThrownBy(() -> new RecordingVisitor(-1)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void maxDepthZeroVisitsOnlyRoot() { - var paragraph = paragraphTree(); - var visitor = new RecordingVisitor(0); - - paragraph.accept(visitor); - - assertThat(visitor.visited).containsExactly("paragraph"); - } - - @Test - public void maxDepthOneVisitsDirectChildrenButNotGrandchildren() { - var paragraph = paragraphTree(); - var visitor = new RecordingVisitor(1); - - paragraph.accept(visitor); - - assertThat(visitor.visited).containsExactly("paragraph", "emphasis", "text:tail"); - } @Test public void replacingNodeInVisitorShouldNotDestroyVisitOrder() { @@ -63,39 +34,4 @@ private static void assertCode(String expectedLiteral, Node node) { Code code = (Code) node; assertThat(code.getLiteral()).isEqualTo(expectedLiteral); } - - private static Paragraph paragraphTree() { - var paragraph = new Paragraph(); - var emphasis = new Emphasis(); - emphasis.appendChild(new Text("nested")); - paragraph.appendChild(emphasis); - paragraph.appendChild(new Text("tail")); - return paragraph; - } - - private static final class RecordingVisitor extends AbstractVisitor { - private final List visited = new ArrayList<>(); - - private RecordingVisitor(int maxDepth) { - super(maxDepth); - } - - @Override - public void visit(Paragraph paragraph) { - visited.add("paragraph"); - super.visit(paragraph); - } - - @Override - public void visit(Emphasis emphasis) { - visited.add("emphasis"); - super.visit(emphasis); - } - - @Override - public void visit(Text text) { - visited.add("text:" + text.getLiteral()); - super.visit(text); - } - } } diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 6164759ec..5550df225 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -206,6 +206,55 @@ public void maxOpenBlockParsersAlsoLimitsMixedListAndBlockQuoteNesting() { assertThat(deepestParagraph.getNext()).isNull(); } + @Test + public void maxOpenBlockParsersAlignsWithVisitorDepthAtDocumentRoot() { + var parser = Parser.builder().maxOpenBlockParsers(5).build(); + + var document = parser.parse(String.join("\n", + "- level1", + " > level2", + " > > level3", + " > > > level4")); + var listBlock = document.getFirstChild(); + var listItem = listBlock.getFirstChild(); + var blockQuote1 = listItem.getLastChild(); + var blockQuote2 = blockQuote1.getLastChild(); + var deepestParagraph = blockQuote2.getLastChild(); + var depthFiveVisitor = new RecordingVisitor(5); + var unlimitedVisitor = new RecordingVisitor(); + + assertThat(depth(deepestParagraph.getFirstChild())).isEqualTo(6); + assertThat(depth(deepestParagraph.getLastChild())).isEqualTo(6); + + document.accept(depthFiveVisitor); + document.accept(unlimitedVisitor); + + assertThat(depthFiveVisitor.visited).containsExactly( + "document", + "bulletList", + "listItem", + "paragraph", + "text:level1", + "blockQuote", + "paragraph", + "text:level2", + "blockQuote", + "paragraph"); + assertThat(unlimitedVisitor.visited).containsExactly( + "document", + "bulletList", + "listItem", + "paragraph", + "text:level1", + "blockQuote", + "paragraph", + "text:level2", + "blockQuote", + "paragraph", + "text:level3", + "text:> level4"); + } + private String firstText(Node n) { while (!(n instanceof Text)) { assertThat(n).isNotNull(); @@ -244,4 +293,60 @@ private String alternatingNestedList(int levels) { } return String.join("\n", lines); } + + private int depth(Node node) { + int depth = 0; + while (node.getParent() != null) { + node = node.getParent(); + depth++; + } + return depth; + } + + private static final class RecordingVisitor extends AbstractVisitor { + private final List visited = new ArrayList<>(); + + private RecordingVisitor() { + } + + private RecordingVisitor(int maxDepth) { + super(maxDepth); + } + + @Override + public void visit(Document document) { + visited.add("document"); + super.visit(document); + } + + @Override + public void visit(BulletList bulletList) { + visited.add("bulletList"); + super.visit(bulletList); + } + + @Override + public void visit(ListItem listItem) { + visited.add("listItem"); + super.visit(listItem); + } + + @Override + public void visit(BlockQuote blockQuote) { + visited.add("blockQuote"); + super.visit(blockQuote); + } + + @Override + public void visit(Paragraph paragraph) { + visited.add("paragraph"); + super.visit(paragraph); + } + + @Override + public void visit(Text text) { + visited.add("text:" + text.getLiteral()); + super.visit(text); + } + } } From 2378c7cbe03e2801063e84eea37d6b873b154417 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 13:00:29 +1100 Subject: [PATCH 257/345] Apply suggestion from @robinst Co-authored-by: Robin Stocker --- commonmark/src/main/java/org/commonmark/parser/Parser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index dc4decf28..8faac789b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -204,7 +204,7 @@ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { } /** - * Limit how many non-document block parsers may be open at once while parsing. + * Limit how many block parsers may be open at once while parsing. *

      * Once the limit is reached, additional block starts are treated as plain text instead of * creating deeper nested block structure. From bef429e670479a13af66b2ef74bb39094dd53014 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 13:01:27 +1100 Subject: [PATCH 258/345] review --- .../src/test/java/org/commonmark/test/ParserTest.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 5550df225..89df86407 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -2,9 +2,8 @@ import org.commonmark.node.*; import org.commonmark.parser.*; -import org.commonmark.parser.block.*; import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.renderer.text.TextContentRenderer; +import org.commonmark.renderer.markdown.MarkdownRenderer; import org.commonmark.testutil.TestResources; import org.junit.jupiter.api.Test; @@ -16,8 +15,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -279,7 +276,7 @@ private Paragraph deepestStructuredParagraph(Node document, int levels) { } private String renderText(Node node) { - return TextContentRenderer.builder().build().render(node).trim(); + return MarkdownRenderer.builder().build().render(node).trim(); } private String alternatingNestedList(int levels) { From b9dd2d5747aa8d04070811b098e2032cf9556ecd Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 13:08:56 +1100 Subject: [PATCH 259/345] remove stale test --- .../java/org/commonmark/test/ParserTest.java | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 89df86407..2427347ea 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -203,55 +203,6 @@ public void maxOpenBlockParsersAlsoLimitsMixedListAndBlockQuoteNesting() { assertThat(deepestParagraph.getNext()).isNull(); } - @Test - public void maxOpenBlockParsersAlignsWithVisitorDepthAtDocumentRoot() { - var parser = Parser.builder().maxOpenBlockParsers(5).build(); - - var document = parser.parse(String.join("\n", - "- level1", - " > level2", - " > > level3", - " > > > level4")); - var listBlock = document.getFirstChild(); - var listItem = listBlock.getFirstChild(); - var blockQuote1 = listItem.getLastChild(); - var blockQuote2 = blockQuote1.getLastChild(); - var deepestParagraph = blockQuote2.getLastChild(); - var depthFiveVisitor = new RecordingVisitor(5); - var unlimitedVisitor = new RecordingVisitor(); - - assertThat(depth(deepestParagraph.getFirstChild())).isEqualTo(6); - assertThat(depth(deepestParagraph.getLastChild())).isEqualTo(6); - - document.accept(depthFiveVisitor); - document.accept(unlimitedVisitor); - - assertThat(depthFiveVisitor.visited).containsExactly( - "document", - "bulletList", - "listItem", - "paragraph", - "text:level1", - "blockQuote", - "paragraph", - "text:level2", - "blockQuote", - "paragraph"); - assertThat(unlimitedVisitor.visited).containsExactly( - "document", - "bulletList", - "listItem", - "paragraph", - "text:level1", - "blockQuote", - "paragraph", - "text:level2", - "blockQuote", - "paragraph", - "text:level3", - "text:> level4"); - } - private String firstText(Node n) { while (!(n instanceof Text)) { assertThat(n).isNotNull(); From 0308e3494a8945e370b6b0833ac72346d923ff32 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 13:13:55 +1100 Subject: [PATCH 260/345] compile --- .../java/org/commonmark/test/ParserTest.java | 51 +------------------ 1 file changed, 2 insertions(+), 49 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 2427347ea..337196c56 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -173,7 +173,7 @@ public void maxOpenBlockParsersDegradesTheNinthLogicalListLevelToPlainText() { var document = parser.parse(alternatingNestedList(9)); var deepestParagraph = deepestStructuredParagraph(document, 8); - assertThat(renderText(deepestParagraph)).isEqualTo("level8\n- level9"); + assertThat(renderText(deepestParagraph)).isEqualTo("level8\n\\- level9"); assertThat(deepestParagraph.getNext()).isNull(); } @@ -199,7 +199,7 @@ public void maxOpenBlockParsersAlsoLimitsMixedListAndBlockQuoteNesting() { var deepestParagraph = blockQuote2.getLastChild(); assertThat(deepestParagraph).isInstanceOf(Paragraph.class); - assertThat(renderText(deepestParagraph)).isEqualTo("level3\n> level4"); + assertThat(renderText(deepestParagraph)).isEqualTo("level3\n\\> level4"); assertThat(deepestParagraph.getNext()).isNull(); } @@ -250,51 +250,4 @@ private int depth(Node node) { } return depth; } - - private static final class RecordingVisitor extends AbstractVisitor { - private final List visited = new ArrayList<>(); - - private RecordingVisitor() { - } - - private RecordingVisitor(int maxDepth) { - super(maxDepth); - } - - @Override - public void visit(Document document) { - visited.add("document"); - super.visit(document); - } - - @Override - public void visit(BulletList bulletList) { - visited.add("bulletList"); - super.visit(bulletList); - } - - @Override - public void visit(ListItem listItem) { - visited.add("listItem"); - super.visit(listItem); - } - - @Override - public void visit(BlockQuote blockQuote) { - visited.add("blockQuote"); - super.visit(blockQuote); - } - - @Override - public void visit(Paragraph paragraph) { - visited.add("paragraph"); - super.visit(paragraph); - } - - @Override - public void visit(Text text) { - visited.add("text:" + text.getLiteral()); - super.visit(text); - } - } } From 61200f9c27cb7b8706b0754ca958a5822bdf801b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 31 Mar 2026 14:04:40 +1100 Subject: [PATCH 261/345] Update commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java --- .../commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java index b524adc05..8008fc8dd 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java @@ -87,6 +87,7 @@ private void tryConvertToAlert(BlockQuote blockQuote) { // Valid alert. Create Alert node and transfer children. var alert = new Alert(type); + alert.setSourceSpans(blockQuote.getSourceSpans()); blockQuote.insertAfter(alert); // Remove the marker text from the first paragraph From e2e27557e475588652902bb261ad0bb8f3be094e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 31 Mar 2026 14:20:29 +1100 Subject: [PATCH 262/345] README: Add section about alerts --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index a917167f6..b9e09a1a9 100644 --- a/README.md +++ b/README.md @@ -333,6 +333,19 @@ Enables tables using pipes as in [GitHub Flavored Markdown][gfm-tables]. Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. +### Alerts + +Adds support for GitHub-style alerts (also known as callouts or admonitions) as described [here](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts), e.g.: + +``` +> [!NOTE] +> The text of the note. +``` + +As types you can use NOTE, TIP, IMPORTANT, WARNING, CAUTION; or configure the extension to add additional ones. + +Use class `AlertsExtension` in artifact `commonmark-ext-gfm-alerts`. + ### Footnotes Enables footnotes like in [GitHub](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#footnotes) From 66210650c3bee8c3129fb4239edf93d902206d20 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 31 Mar 2026 14:27:24 +1100 Subject: [PATCH 263/345] Add alerts extension to integration tests --- commonmark-integration-test/pom.xml | 4 ++++ .../src/test/java/org/commonmark/integration/Extensions.java | 2 ++ pom.xml | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 3433b42d4..8e52dd383 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -28,6 +28,10 @@ org.commonmark commonmark-ext-ins + + org.commonmark + commonmark-ext-gfm-alerts + org.commonmark commonmark-ext-gfm-strikethrough diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java index 8df0408cb..9090c797f 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java @@ -4,6 +4,7 @@ import org.commonmark.ext.autolink.AutolinkExtension; import org.commonmark.ext.footnotes.FootnotesExtension; import org.commonmark.ext.front.matter.YamlFrontMatterExtension; +import org.commonmark.ext.gfm.alerts.AlertsExtension; import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; import org.commonmark.ext.gfm.tables.TablesExtension; import org.commonmark.ext.image.attributes.ImageAttributesExtension; @@ -19,6 +20,7 @@ public class Extensions { FootnotesExtension.create(), ImageAttributesExtension.create(), InsExtension.create(), + AlertsExtension.create(), StrikethroughExtension.create(), TablesExtension.create(), TaskListItemsExtension.create(), diff --git a/pom.xml b/pom.xml index 58c3ead2c..1c9a53f28 100644 --- a/pom.xml +++ b/pom.xml @@ -155,6 +155,11 @@ commonmark-ext-ins 0.27.2-SNAPSHOT + + org.commonmark + commonmark-ext-gfm-alerts + 0.27.2-SNAPSHOT + org.commonmark commonmark-ext-gfm-strikethrough From 873a861938a28653002549a1c418a1ff4b519657 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 31 Mar 2026 14:20:37 +1100 Subject: [PATCH 264/345] Prepare CHANGELOG for version 0.28.0 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b973b700f..399071347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## [0.28.0] - 2026-03-31 +### Added +- New extension for alerts (aka callouts/admonitions) + - Syntax: + ``` + > [!NOTE] + > The text of the note. + ``` + - As types you can use NOTE, TIP, IMPORTANT, WARNING, CAUTION; or configure the + extension to add additional ones. + - Use class `AlertsExtension` in artifact `commonmark-ext-gfm-alerts` (#420) +- New option `maxOpenBlockParsers` for `Parser.Builder` to set an overall limit + for the depth of block parsing. If set, any nesting beyond the limit will be + parsed as paragraph text instead. The default remains unlimited. + ## [0.27.1] - 2026-01-14 ### Fixed - Line(s) after a hard line break would sometimes also get an unwanted hard @@ -518,6 +533,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.28.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.1...commonmark-parent-0.28.0 [0.27.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.0...commonmark-parent-0.27.1 [0.27.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.26.0...commonmark-parent-0.27.0 [0.26.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.1...commonmark-parent-0.26.0 From d278947053b678c639bc888d5410cb507853cf59 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 31 Mar 2026 14:31:56 +1100 Subject: [PATCH 265/345] mvn versions:set -DnewVersion=0.28.0-SNAPSHOT --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-alerts/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 26 ++++++++++++------------ 14 files changed, 26 insertions(+), 26 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index a99fd0b8c..79d8db07a 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 8bb88d74a..14a4a9290 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-alerts/pom.xml b/commonmark-ext-gfm-alerts/pom.xml index 5235af6b2..7780e4e2d 100644 --- a/commonmark-ext-gfm-alerts/pom.xml +++ b/commonmark-ext-gfm-alerts/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-gfm-alerts diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index f6cedc69a..8c9a6b296 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 4e94f623c..37ead1ce6 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 707948a10..c2be557e7 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 456ac04a2..19ea007a9 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index a06c27b29..7288b397b 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 0fc164672..bdd592d78 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 860cfbac7..85b3ad219 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 8e52dd383..6b1d76a65 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 699adb3de..063b8af46 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 76dad6c83..650a5562a 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 1c9a53f28..ace89ff32 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -133,62 +133,62 @@ org.commonmark commonmark - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-ins - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-gfm-alerts - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-test-util - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT From 9e30657b678fd41b29ed3a957c3dd6a92fb445e1 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Tue, 31 Mar 2026 03:40:37 +0000 Subject: [PATCH 266/345] [maven-release-plugin] prepare release commonmark-parent-0.28.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-alerts/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 28 ++++++++++++------------ 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 79d8db07a..818ccf4fb 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 14a4a9290..a81dcbfd1 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-alerts/pom.xml b/commonmark-ext-gfm-alerts/pom.xml index 7780e4e2d..4e5b4e1cc 100644 --- a/commonmark-ext-gfm-alerts/pom.xml +++ b/commonmark-ext-gfm-alerts/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-gfm-alerts diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 8c9a6b296..3b7afb3b1 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 37ead1ce6..ed4b5276b 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index c2be557e7..a5cb099cf 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 19ea007a9..07415ec8c 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 7288b397b..0a41c4dae 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index bdd592d78..7f96168ef 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 85b3ad219..83185bad3 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 6b1d76a65..e50de5bc6 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 063b8af46..542eedc2e 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 650a5562a..673479fd4 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark diff --git a/pom.xml b/pom.xml index ace89ff32..c43a5aed5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -133,62 +133,62 @@ org.commonmark commonmark - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-autolink - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-footnotes - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-image-attributes - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-ins - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-gfm-alerts - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-gfm-strikethrough - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-gfm-tables - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-heading-anchor - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-task-list-items - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-yaml-front-matter - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-test-util - 0.28.0-SNAPSHOT + 0.28.0 @@ -315,7 +315,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - HEAD + commonmark-parent-0.28.0 From ba867e1e63fcabfb08511ce868707f0c1f20f453 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Tue, 31 Mar 2026 03:40:38 +0000 Subject: [PATCH 267/345] [maven-release-plugin] prepare for next development iteration --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-alerts/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 28 ++++++++++++------------ 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 818ccf4fb..2cc4d53ca 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index a81dcbfd1..0d9e2f30c 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-alerts/pom.xml b/commonmark-ext-gfm-alerts/pom.xml index 4e5b4e1cc..02ecbf802 100644 --- a/commonmark-ext-gfm-alerts/pom.xml +++ b/commonmark-ext-gfm-alerts/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-gfm-alerts diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 3b7afb3b1..9d8f55e5f 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index ed4b5276b..5bd323168 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index a5cb099cf..26d2d19b1 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 07415ec8c..e646bc3fd 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 0a41c4dae..48481c073 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 7f96168ef..4359f8707 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 83185bad3..e6822f771 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index e50de5bc6..7e0048a73 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 542eedc2e..6a9c342cc 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 673479fd4..4e060edaa 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index c43a5aed5..f12805316 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -133,62 +133,62 @@ org.commonmark commonmark - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-ins - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-gfm-alerts - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-test-util - 0.28.0 + 0.28.1-SNAPSHOT @@ -315,7 +315,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - commonmark-parent-0.28.0 + HEAD From 41586ddabdee9d455c1b8b7c816bd5f0f86647a5 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 31 Mar 2026 14:52:18 +1100 Subject: [PATCH 268/345] Bump versions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b9e09a1a9..845226729 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.27.1 + 0.28.0 ``` @@ -291,7 +291,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.27.1 + 0.28.0 ``` From 14aef15337de7b9e489be7feb93d0c34b91ffdf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari-Matti=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Apr 2026 18:18:17 +0300 Subject: [PATCH 269/345] Remove redundant overrides --- .../matter/internal/YamlFrontMatterBlockParser.java | 10 ---------- .../org/commonmark/internal/DocumentBlockParser.java | 5 ----- 2 files changed, 15 deletions(-) diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java index 469cf4e2f..f7f03c001 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java @@ -4,8 +4,6 @@ import org.commonmark.ext.front.matter.YamlFrontMatterNode; import org.commonmark.node.Block; import org.commonmark.node.Document; -import org.commonmark.parser.InlineParser; -import org.commonmark.parser.SourceLine; import org.commonmark.parser.block.*; import java.util.ArrayList; @@ -37,10 +35,6 @@ public Block getBlock() { return block; } - @Override - public void addLine(SourceLine line) { - } - @Override public BlockContinue tryContinue(ParserState parserState) { final CharSequence line = parserState.getLine().getContent(); @@ -91,10 +85,6 @@ public BlockContinue tryContinue(ParserState parserState) { } } - @Override - public void parseInlines(InlineParser inlineParser) { - } - private static String parseString(String s) { // Limited parsing of https://yaml.org/spec/1.2.2/#73-flow-scalar-styles // We assume input is well-formed and otherwise treat it as a plain string. In a real diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentBlockParser.java index db3d3854f..4b51275ee 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentBlockParser.java @@ -2,7 +2,6 @@ import org.commonmark.node.Block; import org.commonmark.node.Document; -import org.commonmark.parser.SourceLine; import org.commonmark.parser.block.AbstractBlockParser; import org.commonmark.parser.block.BlockContinue; import org.commonmark.parser.block.ParserState; @@ -31,8 +30,4 @@ public BlockContinue tryContinue(ParserState state) { return BlockContinue.atIndex(state.getIndex()); } - @Override - public void addLine(SourceLine line) { - } - } From 3e440b8088595c638dcf2b2ca7f0a8968c56f2dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari-Matti=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Apr 2026 18:18:47 +0300 Subject: [PATCH 270/345] Remove redundant variable --- .../org/commonmark/ext/front/matter/YamlFrontMatterTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java index db17d4a4e..e0555dc0a 100644 --- a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java +++ b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java @@ -322,8 +322,7 @@ private Map> getFrontMatter(String input) { Node document = PARSER.parse(input); document.accept(visitor); - Map> data = visitor.getData(); - return data; + return visitor.getData(); } // Custom node for tests From 0397aa713fde94c6b1a4f80d95487a6b496f9bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari-Matti=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Apr 2026 18:19:56 +0300 Subject: [PATCH 271/345] Replace anonymous classes with method refs --- .../gfm/strikethrough/StrikethroughExtension.java | 14 ++------------ .../commonmark/ext/gfm/tables/TablesExtension.java | 14 ++------------ .../java/org/commonmark/ext/ins/InsExtension.java | 14 ++------------ .../task/list/items/TaskListItemsExtension.java | 7 +------ .../org/commonmark/renderer/html/HtmlRenderer.java | 7 +------ .../renderer/text/TextContentRenderer.java | 7 +------ .../org/commonmark/test/SourceSpanRenderer.java | 2 +- .../java/org/commonmark/test/UsageExampleTest.java | 7 +------ 8 files changed, 11 insertions(+), 61 deletions(-) diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java index 364205aed..3f5988f00 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java @@ -77,22 +77,12 @@ public void extend(Parser.Builder parserBuilder) { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { - @Override - public NodeRenderer create(HtmlNodeRendererContext context) { - return new StrikethroughHtmlNodeRenderer(context); - } - }); + rendererBuilder.nodeRendererFactory(StrikethroughHtmlNodeRenderer::new); } @Override public void extend(TextContentRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new TextContentNodeRendererFactory() { - @Override - public NodeRenderer create(TextContentNodeRendererContext context) { - return new StrikethroughTextContentNodeRenderer(context); - } - }); + rendererBuilder.nodeRendererFactory(StrikethroughTextContentNodeRenderer::new); } @Override diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java index f754b8276..b49b9dab4 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java @@ -49,22 +49,12 @@ public void extend(Parser.Builder parserBuilder) { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { - @Override - public NodeRenderer create(HtmlNodeRendererContext context) { - return new TableHtmlNodeRenderer(context); - } - }); + rendererBuilder.nodeRendererFactory(TableHtmlNodeRenderer::new); } @Override public void extend(TextContentRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new TextContentNodeRendererFactory() { - @Override - public NodeRenderer create(TextContentNodeRendererContext context) { - return new TableTextContentNodeRenderer(context); - } - }); + rendererBuilder.nodeRendererFactory(TableTextContentNodeRenderer::new); } @Override diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java index e8a53e59a..e16728de2 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java @@ -46,22 +46,12 @@ public void extend(Parser.Builder parserBuilder) { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { - @Override - public NodeRenderer create(HtmlNodeRendererContext context) { - return new InsHtmlNodeRenderer(context); - } - }); + rendererBuilder.nodeRendererFactory(InsHtmlNodeRenderer::new); } @Override public void extend(TextContentRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new TextContentNodeRendererFactory() { - @Override - public NodeRenderer create(TextContentNodeRendererContext context) { - return new InsTextContentNodeRenderer(context); - } - }); + rendererBuilder.nodeRendererFactory(InsTextContentNodeRenderer::new); } @Override diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java index 9bf0a2155..873aacdfd 100644 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java @@ -35,11 +35,6 @@ public void extend(Parser.Builder parserBuilder) { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { - @Override - public NodeRenderer create(HtmlNodeRendererContext context) { - return new TaskListItemHtmlNodeRenderer(context); - } - }); + rendererBuilder.nodeRendererFactory(TaskListItemHtmlNodeRenderer::new); } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java index 386abebf0..6c3122421 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java @@ -41,12 +41,7 @@ private HtmlRenderer(Builder builder) { this.nodeRendererFactories = new ArrayList<>(builder.nodeRendererFactories.size() + 1); this.nodeRendererFactories.addAll(builder.nodeRendererFactories); // Add as last. This means clients can override the rendering of core nodes if they want. - this.nodeRendererFactories.add(new HtmlNodeRendererFactory() { - @Override - public NodeRenderer create(HtmlNodeRendererContext context) { - return new CoreHtmlNodeRenderer(context); - } - }); + this.nodeRendererFactories.add(CoreHtmlNodeRenderer::new); } /** diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java index d64d0c7ef..572fceda8 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java @@ -24,12 +24,7 @@ private TextContentRenderer(Builder builder) { this.nodeRendererFactories = new ArrayList<>(builder.nodeRendererFactories.size() + 1); this.nodeRendererFactories.addAll(builder.nodeRendererFactories); // Add as last. This means clients can override the rendering of core nodes if they want. - this.nodeRendererFactories.add(new TextContentNodeRendererFactory() { - @Override - public NodeRenderer create(TextContentNodeRendererContext context) { - return new CoreTextContentNodeRenderer(context); - } - }); + this.nodeRendererFactories.add(CoreTextContentNodeRenderer::new); } /** diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java b/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java index c29aac61e..597a1e31c 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java @@ -43,7 +43,7 @@ public static String renderWithInputIndex(Node document, String source) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < source.length(); i++) { - markers.getOrDefault(i, List.of()).forEach(marker -> sb.append(marker)); + markers.getOrDefault(i, List.of()).forEach(sb::append); sb.append(source.charAt(i)); } return sb.toString(); diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 20cd9f5ab..6f88d4990 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -94,12 +94,7 @@ public AttributeProvider create(AttributeProviderContext context) { public void customizeRendering() { Parser parser = Parser.builder().build(); HtmlRenderer renderer = HtmlRenderer.builder() - .nodeRendererFactory(new HtmlNodeRendererFactory() { - @Override - public NodeRenderer create(HtmlNodeRendererContext context) { - return new IndentedCodeBlockNodeRenderer(context); - } - }) + .nodeRendererFactory(IndentedCodeBlockNodeRenderer::new) .build(); Node document = parser.parse("Example:\n\n code"); From 6d1112237813f51678b4aab599e44625838ab08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari-Matti=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Apr 2026 18:20:20 +0300 Subject: [PATCH 272/345] Replace anonymous classes with lambdas --- .../ext/gfm/alerts/AlertsExtension.java | 7 +- .../commonmark/ext/gfm/tables/TablesTest.java | 44 ++++------ .../anchor/HeadingAnchorExtension.java | 7 +- .../attributes/ImageAttributesExtension.java | 7 +- .../java/org/commonmark/ui/DingusApp.java | 7 +- .../commonmark/internal/util/Escaping.java | 46 +++++------ .../org/commonmark/test/HtmlRendererTest.java | 80 +++++++------------ .../java/org/commonmark/test/ParserTest.java | 15 +--- .../org/commonmark/test/UsageExampleTest.java | 7 +- 9 files changed, 68 insertions(+), 152 deletions(-) diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index 3990034d2..16c36b221 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -55,12 +55,7 @@ public void extend(Parser.Builder parserBuilder) { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { - @Override - public NodeRenderer create(HtmlNodeRendererContext context) { - return new AlertHtmlNodeRenderer(context, customTypes); - } - }); + rendererBuilder.nodeRendererFactory(context -> new AlertHtmlNodeRenderer(context, customTypes)); } @Override diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java index 3f4b37d54..8d04d175e 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -718,25 +718,17 @@ public void interruptsParagraph() { @Test public void attributeProviderIsApplied() { - AttributeProviderFactory factory = new AttributeProviderFactory() { - @Override - public AttributeProvider create(AttributeProviderContext context) { - return new AttributeProvider() { - @Override - public void setAttributes(Node node, String tagName, Map attributes) { - if (node instanceof TableBlock) { - attributes.put("test", "block"); - } else if (node instanceof TableHead) { - attributes.put("test", "head"); - } else if (node instanceof TableBody) { - attributes.put("test", "body"); - } else if (node instanceof TableRow) { - attributes.put("test", "row"); - } else if (node instanceof TableCell) { - attributes.put("test", "cell"); - } - } - }; + AttributeProviderFactory factory = context -> (node, tagName, attributes) -> { + if (node instanceof TableBlock) { + attributes.put("test", "block"); + } else if (node instanceof TableHead) { + attributes.put("test", "head"); + } else if (node instanceof TableBody) { + attributes.put("test", "body"); + } else if (node instanceof TableRow) { + attributes.put("test", "row"); + } else if (node instanceof TableCell) { + attributes.put("test", "cell"); } }; HtmlRenderer renderer = HtmlRenderer.builder() @@ -762,17 +754,9 @@ public void setAttributes(Node node, String tagName, Map attribu @Test public void columnWidthIsRecorded() { - AttributeProviderFactory factory = new AttributeProviderFactory() { - @Override - public AttributeProvider create(AttributeProviderContext context) { - return new AttributeProvider() { - @Override - public void setAttributes(Node node, String tagName, Map attributes) { - if (node instanceof TableCell && "th".equals(tagName)) { - attributes.put("width", ((TableCell) node).getWidth() + "em"); - } - } - }; + AttributeProviderFactory factory = context -> (node, tagName, attributes) -> { + if (node instanceof TableCell && "th".equals(tagName)) { + attributes.put("width", ((TableCell) node).getWidth() + "em"); } }; HtmlRenderer renderer = HtmlRenderer.builder() diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java index cee414da2..970aeda99 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java @@ -55,12 +55,7 @@ public static Builder builder() { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.attributeProviderFactory(new AttributeProviderFactory() { - @Override - public AttributeProvider create(AttributeProviderContext context) { - return HeadingIdAttributeProvider.create(defaultId, idPrefix, idSuffix); - } - }); + rendererBuilder.attributeProviderFactory(context -> HeadingIdAttributeProvider.create(defaultId, idPrefix, idSuffix)); } public static class Builder { diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java index 28c6abab2..20d8aa86d 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java @@ -35,11 +35,6 @@ public void extend(Parser.Builder parserBuilder) { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.attributeProviderFactory(new AttributeProviderFactory() { - @Override - public AttributeProvider create(AttributeProviderContext context) { - return ImageAttributesAttributeProvider.create(); - } - }); + rendererBuilder.attributeProviderFactory(context -> ImageAttributesAttributeProvider.create()); } } diff --git a/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java b/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java index 0e98386bb..b6564920f 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java @@ -79,12 +79,7 @@ public void changedUpdate(DocumentEvent e) { tabbedPane.addTab("HTML source", htmlSourceRendererOutput); tabbedPane.addTab("Plain text", textRendererOutput); - tabbedPane.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - updateOutput(input.getText()); - } - }); + tabbedPane.addChangeListener(e -> updateOutput(input.getText())); input.setText("# Example\n" + "Enter text *here* and see how it renders on the right.\n\n" + diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java index 3350003c0..1c96bc00c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java @@ -25,36 +25,30 @@ public class Escaping { private static final Pattern WHITESPACE = Pattern.compile("[ \t\r\n]+"); - private static final Replacer UNESCAPE_REPLACER = new Replacer() { - @Override - public void replace(String input, StringBuilder sb) { - if (input.charAt(0) == '\\') { - sb.append(input, 1, input.length()); - } else { - sb.append(Html5Entities.entityToString(input)); - } + private static final Replacer UNESCAPE_REPLACER = (input, sb) -> { + if (input.charAt(0) == '\\') { + sb.append(input, 1, input.length()); + } else { + sb.append(Html5Entities.entityToString(input)); } }; - private static final Replacer URI_REPLACER = new Replacer() { - @Override - public void replace(String input, StringBuilder sb) { - if (input.startsWith("%")) { - if (input.length() == 3) { - // Already percent-encoded, preserve - sb.append(input); - } else { - // %25 is the percent-encoding for % - sb.append("%25"); - sb.append(input, 1, input.length()); - } + private static final Replacer URI_REPLACER = (input, sb) -> { + if (input.startsWith("%")) { + if (input.length() == 3) { + // Already percent-encoded, preserve + sb.append(input); } else { - byte[] bytes = input.getBytes(StandardCharsets.UTF_8); - for (byte b : bytes) { - sb.append('%'); - sb.append(HEX_DIGITS[(b >> 4) & 0xF]); - sb.append(HEX_DIGITS[b & 0xF]); - } + // %25 is the percent-encoding for % + sb.append("%25"); + sb.append(input, 1, input.length()); + } + } else { + byte[] bytes = input.getBytes(StandardCharsets.UTF_8); + for (byte b : bytes) { + sb.append('%'); + sb.append(HEX_DIGITS[(b >> 4) & 0xF]); + sb.append(HEX_DIGITS[b & 0xF]); } } }; diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 02d970949..92751147a 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -162,23 +162,15 @@ public void percentEncodeUrl() { @Test public void attributeProviderForCodeBlock() { - AttributeProviderFactory custom = new AttributeProviderFactory() { - @Override - public AttributeProvider create(AttributeProviderContext context) { - return new AttributeProvider() { - @Override - public void setAttributes(Node node, String tagName, Map attributes) { - if (node instanceof FencedCodeBlock && tagName.equals("code")) { - FencedCodeBlock fencedCodeBlock = (FencedCodeBlock) node; - // Remove the default attribute for info - attributes.remove("class"); - // Put info in custom attribute instead - attributes.put("data-custom", fencedCodeBlock.getInfo()); - } else if (node instanceof FencedCodeBlock && tagName.equals("pre")) { - attributes.put("data-code-block", "fenced"); - } - } - }; + AttributeProviderFactory custom = context -> (node, tagName, attributes) -> { + if (node instanceof FencedCodeBlock && tagName.equals("code")) { + FencedCodeBlock fencedCodeBlock = (FencedCodeBlock) node; + // Remove the default attribute for info + attributes.remove("class"); + // Put info in custom attribute instead + attributes.put("data-custom", fencedCodeBlock.getInfo()); + } else if (node instanceof FencedCodeBlock && tagName.equals("pre")) { + attributes.put("data-code-block", "fenced"); } }; @@ -192,18 +184,10 @@ public void setAttributes(Node node, String tagName, Map attribu @Test public void attributeProviderForImage() { - AttributeProviderFactory custom = new AttributeProviderFactory() { - @Override - public AttributeProvider create(AttributeProviderContext context) { - return new AttributeProvider() { - @Override - public void setAttributes(Node node, String tagName, Map attributes) { - if (node instanceof Image) { - attributes.remove("alt"); - attributes.put("test", "hey"); - } - } - }; + AttributeProviderFactory custom = context -> (node, tagName, attributes) -> { + if (node instanceof Image) { + attributes.remove("alt"); + attributes.put("test", "hey"); } }; @@ -214,18 +198,13 @@ public void setAttributes(Node node, String tagName, Map attribu @Test public void attributeProviderFactoryNewInstanceForEachRender() { - AttributeProviderFactory factory = new AttributeProviderFactory() { + AttributeProviderFactory factory = context -> new AttributeProvider() { + int i = 0; + @Override - public AttributeProvider create(AttributeProviderContext context) { - return new AttributeProvider() { - int i = 0; - - @Override - public void setAttributes(Node node, String tagName, Map attributes) { - attributes.put("key", "" + i); - i++; - } - }; + public void setAttributes(Node node, String tagName, Map attributes) { + attributes.put("key", "" + i); + i++; } }; @@ -237,20 +216,15 @@ public void setAttributes(Node node, String tagName, Map attribu @Test public void overrideNodeRender() { - HtmlNodeRendererFactory nodeRendererFactory = new HtmlNodeRendererFactory() { + HtmlNodeRendererFactory nodeRendererFactory = context -> new NodeRenderer() { + @Override + public Set> getNodeTypes() { + return Set.of(Link.class); + } + @Override - public NodeRenderer create(final HtmlNodeRendererContext context) { - return new NodeRenderer() { - @Override - public Set> getNodeTypes() { - return Set.of(Link.class); - } - - @Override - public void render(Node node) { - context.getWriter().text("test"); - } - }; + public void render(Node node) { + context.getWriter().text("test"); } }; diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 337196c56..82eb1c82d 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -90,20 +90,9 @@ public void indentation() { @Test public void inlineParser() { - final InlineParser fakeInlineParser = new InlineParser() { - @Override - public void parse(SourceLines lines, Node node) { - node.appendChild(new ThematicBreak()); - } - }; - - InlineParserFactory fakeInlineParserFactory = new InlineParserFactory() { + final InlineParser fakeInlineParser = (lines, node) -> node.appendChild(new ThematicBreak()); - @Override - public InlineParser create(InlineParserContext inlineParserContext) { - return fakeInlineParser; - } - }; + InlineParserFactory fakeInlineParserFactory = inlineParserContext -> fakeInlineParser; Parser parser = Parser.builder().inlineParserFactory(fakeInlineParserFactory).build(); String input = "**bold** **bold** ~~strikethrough~~"; diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 6f88d4990..97ac8990e 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -78,12 +78,7 @@ public void sourcePositions() { public void addAttributes() { Parser parser = Parser.builder().build(); HtmlRenderer renderer = HtmlRenderer.builder() - .attributeProviderFactory(new AttributeProviderFactory() { - @Override - public AttributeProvider create(AttributeProviderContext context) { - return new ImageAttributeProvider(); - } - }) + .attributeProviderFactory(context -> new ImageAttributeProvider()) .build(); Node document = parser.parse("![text](/url.png)"); From 8a87d5b1286565e1fe96a727959201b7f5036ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari-Matti=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Apr 2026 18:21:35 +0300 Subject: [PATCH 273/345] Add explicit static keyword for nested classes that do not depend on outer context --- .../src/test/java/org/commonmark/test/UsageExampleTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 97ac8990e..1a3625848 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -96,7 +96,7 @@ public void customizeRendering() { assertThat(renderer.render(document)).isEqualTo("

      Example:

      \n
      code\n
      \n"); } - class WordCountVisitor extends AbstractVisitor { + static class WordCountVisitor extends AbstractVisitor { int wordCount = 0; @@ -112,7 +112,7 @@ public void visit(Text text) { } } - class ImageAttributeProvider implements AttributeProvider { + static class ImageAttributeProvider implements AttributeProvider { @Override public void setAttributes(Node node, String tagName, Map attributes) { if (node instanceof Image) { @@ -121,7 +121,7 @@ public void setAttributes(Node node, String tagName, Map attribu } } - class IndentedCodeBlockNodeRenderer implements NodeRenderer { + static class IndentedCodeBlockNodeRenderer implements NodeRenderer { private final HtmlWriter html; From 867e58c611708cf7e731b0d421289b8eadcf85f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari-Matti=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Apr 2026 18:22:20 +0300 Subject: [PATCH 274/345] Refactor bulk collection operations --- .../attributes/internal/ImageAttributesAttributeProvider.java | 4 +--- .../src/main/java/org/commonmark/internal/DocumentParser.java | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java index edd9c4692..bd89738de 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java @@ -26,9 +26,7 @@ public void setAttributes(Node node, String tagName, final Map a public void visit(CustomNode node) { if (node instanceof ImageAttributes) { ImageAttributes imageAttributes = (ImageAttributes) node; - for (Map.Entry entry : imageAttributes.getAttributes().entrySet()) { - attributes.put(entry.getKey(), entry.getValue()); - } + attributes.putAll(imageAttributes.getAttributes()); // Now that we have used the image attributes we remove the node. imageAttributes.unlink(); } diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 07d97296b..17e7b9c84 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -105,9 +105,8 @@ public static Set> getDefaultBlockParserTypes() { } public static List calculateBlockParserFactories(List customBlockParserFactories, Set> enabledBlockTypes) { - List list = new ArrayList<>(); // By having the custom factories come first, extensions are able to change behavior of core syntax. - list.addAll(customBlockParserFactories); + List list = new ArrayList<>(customBlockParserFactories); for (Class blockType : enabledBlockTypes) { list.add(NODES_TO_CORE_FACTORIES.get(blockType)); } From 8df50617946609975b21f3e791c7d0712cb0b34c Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 10 Apr 2026 15:51:59 +1000 Subject: [PATCH 275/345] Allow customizing HTML attributes for alert title --- CHANGELOG.md | 5 +++++ .../ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 399071347..9c5c67268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## [Unreleased] +### Added +- Allow customizing HTML attributes for alert title `

      ` tag via `AttributeProvider` + ## [0.28.0] - 2026-03-31 ### Added - New extension for alerts (aka callouts/admonitions) @@ -533,6 +537,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[Unreleased]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.28.0...main [0.28.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.1...commonmark-parent-0.28.0 [0.27.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.0...commonmark-parent-0.27.1 [0.27.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.26.0...commonmark-parent-0.27.0 diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java index 763b2e217..ca562ba33 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -34,7 +34,7 @@ protected void renderAlert(Alert alert) { htmlWriter.line(); // Render alert title - htmlWriter.tag("p", Map.of("class", "markdown-alert-title")); + htmlWriter.tag("p", context.extendAttributes(alert, "p", Map.of("class", "markdown-alert-title"))); htmlWriter.text(getAlertTitle(type)); htmlWriter.tag("/p"); htmlWriter.line(); From 6b1e80b6b02c5a8d50cc119949a8e71560f0be28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari-Matti=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Apr 2026 18:25:17 +0300 Subject: [PATCH 276/345] Refactor to use String.isEmpty --- .../commonmark/renderer/text/CoreTextContentNodeRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java index ee564cbdb..62a1a054d 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -236,7 +236,7 @@ private void writeText(String text) { private void writeLink(Node node, String title, String destination) { boolean hasChild = node.getFirstChild() != null; boolean hasTitle = title != null && !title.equals(destination); - boolean hasDestination = destination != null && !destination.equals(""); + boolean hasDestination = destination != null && !destination.isEmpty(); if (hasChild) { textContent.write('"'); From f8833db82fd662802a4f237b28fcae488e92e73d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari-Matti=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Apr 2026 18:27:21 +0300 Subject: [PATCH 277/345] Remove redundant label and escape --- .../src/main/java/org/commonmark/internal/util/Escaping.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java index 1c96bc00c..caf802a43 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java @@ -7,7 +7,7 @@ public class Escaping { - public static final String ESCAPABLE = "[!\"#$%&\'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~-]"; + public static final String ESCAPABLE = "[!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~-]"; public static final String ENTITY = "&(?:#x[a-f0-9]{1,6}|#[0-9]{1,7}|[a-z][a-z0-9]{1,31});"; @@ -57,7 +57,6 @@ public static String escapeHtml(String input) { // Avoid building a new string in the majority of cases (nothing to escape) StringBuilder sb = null; - loop: for (int i = 0; i < input.length(); i++) { char c = input.charAt(i); String replacement; @@ -78,7 +77,7 @@ public static String escapeHtml(String input) { if (sb != null) { sb.append(c); } - continue loop; + continue; } if (sb == null) { sb = new StringBuilder(); From ae68c5a1e35066dd7ce99f8d7e233dc0d4f78a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari-Matti=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Apr 2026 18:28:04 +0300 Subject: [PATCH 278/345] Refactor String API calls --- .../commonmark/ext/gfm/tables/internal/TableBlockParser.java | 2 +- .../java/org/commonmark/ext/heading/anchor/IdGenerator.java | 2 +- .../attributes/internal/ImageAttributesDelimiterProcessor.java | 2 +- .../main/java/org/commonmark/internal/util/Html5Entities.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index 57af128d8..49f17194d 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java @@ -274,7 +274,7 @@ public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { List paragraphLines = matchedBlockParser.getParagraphLines().getLines(); - if (paragraphLines.size() >= 1 && Characters.find('|', paragraphLines.get(paragraphLines.size() - 1).getContent(), 0) != -1) { + if (!paragraphLines.isEmpty() && Characters.find('|', paragraphLines.get(paragraphLines.size() - 1).getContent(), 0) != -1) { SourceLine line = state.getLine(); SourceLine separatorLine = line.substring(state.getIndex(), line.getContent().length()); List columns = parseSeparator(separatorLine.getContent()); diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java index 6eb85b6c1..2c37433e2 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java @@ -66,7 +66,7 @@ public static Builder builder() { public String generateId(String text) { String normalizedIdentity = text != null ? normalizeText(text) : defaultIdentifier; - if (normalizedIdentity.length() == 0) { + if (normalizedIdentity.isEmpty()) { normalizedIdentity = defaultIdentifier; } diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java index a335ccadc..06a39ba23 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java @@ -75,7 +75,7 @@ public int process(DelimiterRun openingRun, DelimiterRun closingRun) { node.unlink(); } - if (attributesMap.size() > 0) { + if (!attributesMap.isEmpty()) { ImageAttributes imageAttributes = new ImageAttributes(attributesMap); // The new node is added as a child of the image node to which the attributes apply. diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java b/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java index 8da53c053..d6034ff14 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java @@ -54,7 +54,7 @@ private static Map readEntities() { try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, charset))) { String line; while ((line = bufferedReader.readLine()) != null) { - if (line.length() == 0) { + if (line.isEmpty()) { continue; } int equal = line.indexOf("="); From fed333d4d252888fbc2b6946c192a56302b4fbcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari-Matti=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Apr 2026 18:28:15 +0300 Subject: [PATCH 279/345] Refactor conditional --- commonmark/src/main/java/org/commonmark/node/Node.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/node/Node.java b/commonmark/src/main/java/org/commonmark/node/Node.java index d95a72c60..abbda67f2 100644 --- a/commonmark/src/main/java/org/commonmark/node/Node.java +++ b/commonmark/src/main/java/org/commonmark/node/Node.java @@ -50,11 +50,10 @@ public void appendChild(Node child) { if (this.lastChild != null) { this.lastChild.next = child; child.prev = this.lastChild; - this.lastChild = child; } else { this.firstChild = child; - this.lastChild = child; } + this.lastChild = child; } public void prependChild(Node child) { From 23801acb4063b2ce3f1dd917690c975dda6bee24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari-Matti=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Apr 2026 18:28:47 +0300 Subject: [PATCH 280/345] Remove redundant imports --- .../org/commonmark/ext/gfm/alerts/AlertsExtension.java | 2 -- .../ext/gfm/strikethrough/StrikethroughExtension.java | 4 ---- .../org/commonmark/ext/gfm/tables/TablesExtension.java | 4 ---- .../java/org/commonmark/ext/gfm/tables/TablesTest.java | 3 --- .../ext/heading/anchor/HeadingAnchorExtension.java | 3 --- .../ext/image/attributes/ImageAttributesExtension.java | 3 --- .../main/java/org/commonmark/ext/ins/InsExtension.java | 4 ---- .../ext/task/list/items/TaskListItemsExtension.java | 3 --- .../integration/MarkdownRendererIntegrationTest.java | 10 ---------- .../integration/SourceSpanIntegrationTest.java | 1 - .../commonmark/integration/SpecIntegrationTest.java | 1 - .../src/test/java/org/commonmark/ui/DingusApp.java | 2 -- .../org/commonmark/testutil/RenderingTestCase.java | 2 -- .../java/org/commonmark/testutil/SpecTestCase.java | 2 -- .../org/commonmark/renderer/html/HtmlRenderer.java | 1 - .../commonmark/renderer/text/TextContentRenderer.java | 1 - .../src/test/java/org/commonmark/test/ParserTest.java | 1 - .../test/java/org/commonmark/test/SpecCoreTest.java | 1 - .../java/org/commonmark/test/SpecCrLfCoreTest.java | 1 - 19 files changed, 49 deletions(-) diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index 16c36b221..b9a449854 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -6,8 +6,6 @@ import org.commonmark.ext.gfm.alerts.internal.AlertMarkdownNodeRenderer; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java index 3f5988f00..0571aed84 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java @@ -7,14 +7,10 @@ import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughTextContentNodeRenderer; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; import org.commonmark.renderer.markdown.MarkdownRenderer; -import org.commonmark.renderer.text.TextContentNodeRendererContext; -import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentRenderer; import java.util.Set; diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java index b49b9dab4..4740e9aad 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java @@ -7,14 +7,10 @@ import org.commonmark.ext.gfm.tables.internal.TableTextContentNodeRenderer; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; import org.commonmark.renderer.markdown.MarkdownRenderer; -import org.commonmark.renderer.text.TextContentNodeRendererContext; -import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentRenderer; import java.util.Set; diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java index 8d04d175e..98c3ad4d7 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -4,15 +4,12 @@ import org.commonmark.node.*; import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.AttributeProvider; -import org.commonmark.renderer.html.AttributeProviderContext; import org.commonmark.renderer.html.AttributeProviderFactory; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; import java.util.List; -import java.util.Map; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java index 970aeda99..5921f3c5b 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java @@ -2,9 +2,6 @@ import org.commonmark.Extension; import org.commonmark.ext.heading.anchor.internal.HeadingIdAttributeProvider; -import org.commonmark.renderer.html.AttributeProvider; -import org.commonmark.renderer.html.AttributeProviderContext; -import org.commonmark.renderer.html.AttributeProviderFactory; import org.commonmark.renderer.html.HtmlRenderer; /** diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java index 20d8aa86d..9a4de2628 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java @@ -4,9 +4,6 @@ import org.commonmark.ext.image.attributes.internal.ImageAttributesAttributeProvider; import org.commonmark.ext.image.attributes.internal.ImageAttributesDelimiterProcessor; import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.AttributeProvider; -import org.commonmark.renderer.html.AttributeProviderContext; -import org.commonmark.renderer.html.AttributeProviderFactory; import org.commonmark.renderer.html.HtmlRenderer; /** diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java index e16728de2..3d2974916 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java @@ -7,14 +7,10 @@ import org.commonmark.ext.ins.internal.InsTextContentNodeRenderer; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; import org.commonmark.renderer.markdown.MarkdownRenderer; -import org.commonmark.renderer.text.TextContentNodeRendererContext; -import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentRenderer; import java.util.Set; diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java index 873aacdfd..b84559273 100644 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java @@ -4,9 +4,6 @@ import org.commonmark.ext.task.list.items.internal.TaskListItemHtmlNodeRenderer; import org.commonmark.ext.task.list.items.internal.TaskListItemPostProcessor; import org.commonmark.parser.Parser; -import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.renderer.html.HtmlRenderer; /** diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java index fe14273ab..9e64f9717 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java @@ -1,19 +1,9 @@ package org.commonmark.integration; -import org.commonmark.Extension; -import org.commonmark.ext.autolink.AutolinkExtension; -import org.commonmark.ext.front.matter.YamlFrontMatterExtension; -import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; -import org.commonmark.ext.gfm.tables.TablesExtension; -import org.commonmark.ext.image.attributes.ImageAttributesExtension; -import org.commonmark.ext.ins.InsExtension; -import org.commonmark.ext.task.list.items.TaskListItemsExtension; import org.commonmark.parser.Parser; import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.jupiter.api.Test; -import java.util.List; - import static org.assertj.core.api.Assertions.assertThat; public class MarkdownRendererIntegrationTest { diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java index 171cc51b1..4d18fa937 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java @@ -2,7 +2,6 @@ import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; -import org.commonmark.testutil.example.Example; /** * Spec and all extensions, with source spans enabled. diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java index 07853d402..6dca4024e 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java @@ -2,7 +2,6 @@ import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; -import org.commonmark.testutil.example.Example; import org.commonmark.testutil.SpecTestCase; import org.junit.jupiter.api.Test; diff --git a/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java b/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java index b6564920f..d8132fe25 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java @@ -6,8 +6,6 @@ import java.awt.*; import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java index f7da4c008..b585f4604 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java @@ -1,7 +1,5 @@ package org.commonmark.testutil; -import static org.assertj.core.api.Assertions.assertThat; - public abstract class RenderingTestCase { protected abstract String render(String source); diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java index c29a6a69a..d61afbc4a 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java @@ -4,10 +4,8 @@ import org.commonmark.testutil.example.ExampleReader; import org.junit.jupiter.params.Parameter; import org.junit.jupiter.params.ParameterizedClass; -import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.util.ArrayList; import java.util.List; @ParameterizedClass diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java index 6c3122421..b0264fc72 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java @@ -4,7 +4,6 @@ import org.commonmark.internal.renderer.NodeRendererMap; import org.commonmark.internal.util.Escaping; import org.commonmark.node.*; -import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.Renderer; import java.util.*; diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java index 572fceda8..9a8bcc997 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java @@ -3,7 +3,6 @@ import org.commonmark.Extension; import org.commonmark.internal.renderer.NodeRendererMap; import org.commonmark.node.Node; -import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.Renderer; import java.util.ArrayList; diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 82eb1c82d..4348563bd 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -13,7 +13,6 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.Future; diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java index fefd8fb30..047a1e6cf 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java @@ -6,7 +6,6 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.SpecTestCase; -import org.commonmark.testutil.example.Example; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.fail; diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java index 47ca3da4e..eb0fcd0dd 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java @@ -3,7 +3,6 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.SpecTestCase; -import org.commonmark.testutil.example.Example; import org.junit.jupiter.api.Test; import static org.commonmark.testutil.Asserts.assertRendering; From d4c76680c11bfc6ebac978524a8be4f1ed99be27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari-Matti=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Apr 2026 18:41:54 +0300 Subject: [PATCH 281/345] Remove redundant throws in declarations --- .../org/commonmark/internal/util/LineReaderTest.java | 4 ++-- .../org/commonmark/test/TextContentWriterTest.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java b/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java index b52713846..7d0e0beb7 100644 --- a/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java @@ -100,7 +100,7 @@ private SlowStringReader(String s) { } @Override - public int read(char[] cbuf, int off, int len) throws IOException { + public int read(char[] cbuf, int off, int len) { Objects.checkFromIndexSize(off, len, cbuf.length); if (len == 0) { return 0; @@ -118,7 +118,7 @@ public int read(char[] cbuf, int off, int len) throws IOException { } @Override - public void close() throws IOException { + public void close() { } } } diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java index a9f37792e..4641b616e 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java @@ -8,7 +8,7 @@ public class TextContentWriterTest { @Test - public void whitespace() throws Exception { + public void whitespace() { StringBuilder stringBuilder = new StringBuilder(); TextContentWriter writer = new TextContentWriter(stringBuilder); writer.write("foo"); @@ -18,7 +18,7 @@ public void whitespace() throws Exception { } @Test - public void colon() throws Exception { + public void colon() { StringBuilder stringBuilder = new StringBuilder(); TextContentWriter writer = new TextContentWriter(stringBuilder); writer.write("foo"); @@ -28,7 +28,7 @@ public void colon() throws Exception { } @Test - public void line() throws Exception { + public void line() { StringBuilder stringBuilder = new StringBuilder(); TextContentWriter writer = new TextContentWriter(stringBuilder); writer.write("foo"); @@ -38,7 +38,7 @@ public void line() throws Exception { } @Test - public void writeStripped() throws Exception { + public void writeStripped() { StringBuilder stringBuilder = new StringBuilder(); TextContentWriter writer = new TextContentWriter(stringBuilder); writer.writeStripped("foo\n bar"); @@ -46,7 +46,7 @@ public void writeStripped() throws Exception { } @Test - public void write() throws Exception { + public void write() { StringBuilder stringBuilder = new StringBuilder(); TextContentWriter writer = new TextContentWriter(stringBuilder); writer.writeStripped("foo bar"); From c1f255dbb107e625d9023b3efefd8c14bed55385 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 4 May 2026 15:39:54 +1000 Subject: [PATCH 282/345] Update README.md --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 845226729..7bf598ba3 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,9 @@ import org.commonmark.node.*; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; -Parser parser = Parser.builder().build(); -Node document = parser.parse("This is *Markdown*"); -HtmlRenderer renderer = HtmlRenderer.builder().build(); +var parser = Parser.builder().build(); +var document = parser.parse("This is *Markdown*"); +var renderer = HtmlRenderer.builder().build(); renderer.render(document); // "

      This is Markdown

      \n" ``` @@ -90,14 +90,16 @@ after this. import org.commonmark.node.*; import org.commonmark.renderer.markdown.MarkdownRenderer; -MarkdownRenderer renderer = MarkdownRenderer.builder().build(); -Node document = new Document(); -Heading heading = new Heading(); +// Build document +var heading = new Heading(); heading.setLevel(2); -heading.appendChild(new Text("My title")); +heading.appendChild(new Text("My heading")); +var document = new Document(); document.appendChild(heading); -renderer.render(document); // "## My title\n" +// Render to Markdown +var renderer = MarkdownRenderer.builder().build(); +renderer.render(document); // "## My heading\n" ``` For rendering to plain text with minimal markup, there's also `TextContentRenderer`. From 9777069359bda53b9b29f3a1b828429b2d63c7ab Mon Sep 17 00:00:00 2001 From: Stuart Parker <2979257+stupar73@users.noreply.github.com> Date: Mon, 11 May 2026 14:13:16 +1000 Subject: [PATCH 283/345] Extract base class form TaskListItemHtmlNodeRenderer --- .../internal/TaskListItemHtmlNodeRenderer.java | 9 +-------- .../items/internal/TaskListItemNodeRenderer.java | 13 +++++++++++++ 2 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemNodeRenderer.java diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java index 331b301e9..a27b125c8 100644 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java @@ -2,15 +2,13 @@ import org.commonmark.ext.task.list.items.TaskListItemMarker; import org.commonmark.node.Node; -import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Set; -public class TaskListItemHtmlNodeRenderer implements NodeRenderer { +public class TaskListItemHtmlNodeRenderer extends TaskListItemNodeRenderer { private final HtmlNodeRendererContext context; private final HtmlWriter html; @@ -20,11 +18,6 @@ public TaskListItemHtmlNodeRenderer(HtmlNodeRendererContext context) { this.html = context.getWriter(); } - @Override - public Set> getNodeTypes() { - return Set.of(TaskListItemMarker.class); - } - @Override public void render(Node node) { if (node instanceof TaskListItemMarker) { diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemNodeRenderer.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemNodeRenderer.java new file mode 100644 index 000000000..24efd4e7d --- /dev/null +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemNodeRenderer.java @@ -0,0 +1,13 @@ +package org.commonmark.ext.task.list.items.internal; + +import java.util.Set; +import org.commonmark.ext.task.list.items.TaskListItemMarker; +import org.commonmark.node.Node; +import org.commonmark.renderer.NodeRenderer; + +public abstract class TaskListItemNodeRenderer implements NodeRenderer { + @Override + public Set> getNodeTypes() { + return Set.of(TaskListItemMarker.class); + } +} From 978e4b089646020328a54e6698c2a267b3222ecc Mon Sep 17 00:00:00 2001 From: Stuart Parker <2979257+stupar73@users.noreply.github.com> Date: Mon, 11 May 2026 14:13:16 +1000 Subject: [PATCH 284/345] Add support for Markdown rendering for GFM task list items --- .../list/items/TaskListItemsExtension.java | 24 ++++++++++++- .../TaskListItemMarkdownNodeRenderer.java | 36 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemMarkdownNodeRenderer.java diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java index b84559273..1c89256d3 100644 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java @@ -1,10 +1,16 @@ package org.commonmark.ext.task.list.items; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.ext.task.list.items.internal.TaskListItemHtmlNodeRenderer; +import org.commonmark.ext.task.list.items.internal.TaskListItemMarkdownNodeRenderer; import org.commonmark.ext.task.list.items.internal.TaskListItemPostProcessor; import org.commonmark.parser.Parser; +import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; +import org.commonmark.renderer.markdown.MarkdownRenderer; /** * Extension for adding task list items. @@ -16,7 +22,8 @@ * * @since 0.15.0 */ -public class TaskListItemsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { +public class TaskListItemsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, + MarkdownRenderer.MarkdownRendererExtension { private TaskListItemsExtension() { } @@ -34,4 +41,19 @@ public void extend(Parser.Builder parserBuilder) { public void extend(HtmlRenderer.Builder rendererBuilder) { rendererBuilder.nodeRendererFactory(TaskListItemHtmlNodeRenderer::new); } + + @Override + public void extend(MarkdownRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new TaskListItemMarkdownNodeRenderer(context); + } + + @Override + public Set getSpecialCharacters() { + return Set.of(); + } + }); + } } diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemMarkdownNodeRenderer.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemMarkdownNodeRenderer.java new file mode 100644 index 000000000..9910de893 --- /dev/null +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemMarkdownNodeRenderer.java @@ -0,0 +1,36 @@ +package org.commonmark.ext.task.list.items.internal; + +import org.commonmark.ext.task.list.items.TaskListItemMarker; +import org.commonmark.node.Node; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownWriter; + +public class TaskListItemMarkdownNodeRenderer extends TaskListItemNodeRenderer { + + private final MarkdownNodeRendererContext context; + private final MarkdownWriter writer; + + public TaskListItemMarkdownNodeRenderer(MarkdownNodeRendererContext context) { + this.context = context; + this.writer = context.getWriter(); + } + + @Override + public void render(Node node) { + if (node instanceof TaskListItemMarker) { + TaskListItemMarker taskListItemNode = (TaskListItemMarker) node; + var checkboxFill = taskListItemNode.isChecked() ? "x" : " "; + writer.raw("[" + checkboxFill + "] "); + renderChildren(node); + } + } + + private void renderChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } +} From 45a1b6d59eb39cb3df10ceb58587d91a453b056f Mon Sep 17 00:00:00 2001 From: Stuart Parker <2979257+stupar73@users.noreply.github.com> Date: Mon, 11 May 2026 14:13:16 +1000 Subject: [PATCH 285/345] Add tests --- .../TaskListItemMarkdownRendererTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemMarkdownRendererTest.java diff --git a/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemMarkdownRendererTest.java b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemMarkdownRendererTest.java new file mode 100644 index 000000000..cf73f434c --- /dev/null +++ b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemMarkdownRendererTest.java @@ -0,0 +1,84 @@ +package org.commonmark.ext.task.list.items; + +import java.util.Set; +import org.commonmark.Extension; +import org.commonmark.node.BulletList; +import org.commonmark.node.Document; +import org.commonmark.node.ListItem; +import org.commonmark.node.Node; +import org.commonmark.node.Paragraph; +import org.commonmark.node.Text; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.markdown.MarkdownRenderer; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TaskListItemMarkdownRendererTest { + + private static final Set EXTENSIONS = Set.of(TaskListItemsExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void testCheckedRoundTrip() { + assertRoundTrip("- [x] I am checked\n"); + } + + @Test + public void testUncheckedRoundTrip() { + assertRoundTrip("- [ ] I am unchecked\n"); + } + + @Test + public void testMixedRoundTrip() { + assertRoundTrip("- [x] I am checked\n- [ ] I am unchecked\n"); + } + + @Test + public void testNestedRoundTrip() { + assertRoundTrip("- [ ] I am unchecked\n - [x] I am a checked child\n"); + } + + @Test + public void testFormattingRoundTrip() { + assertRoundTrip("- [x] I am **boldly** checked\n- [ ] I am *italicly* unchecked\n"); + } + + @Test + public void testNonTaskListItemRoundTrip() { + assertRoundTrip("- [x] I am checked\n- [ ] I am unchecked\n- I am not a task item\n"); + } + + @Test + public void testOrderedListRoundTrip() { + assertRoundTrip("1. [x] I am checked\n2. [ ] I am unchecked\n"); + } + + @Test + public void testProgrammaticallyBuilt() { + var doc = new Document(); + var list = new BulletList(); + var item = new ListItem(); + var taskMarker = new TaskListItemMarker(false); + var para = new Paragraph(); + var text = new Text("I am a task"); + para.appendChild(text); + item.appendChild(taskMarker); + item.appendChild(para); + list.appendChild(item); + doc.appendChild(list); + + assertRenderedEquals(doc, "- [ ] I am a task\n"); + } + + private void assertRoundTrip(String input) { + String rendered = RENDERER.render(PARSER.parse(input)); + assertThat(rendered).isEqualTo(input); + } + + private void assertRenderedEquals(Node inputNode, String expectedOutput) { + var renderedOutput = RENDERER.render(inputNode); + assertThat(renderedOutput).isEqualTo(expectedOutput); + } +} From 6f2bbc57ff3cb0db51dc5824696e1afd54d14ee1 Mon Sep 17 00:00:00 2001 From: Stuart Parker <2979257+stupar73@users.noreply.github.com> Date: Mon, 11 May 2026 14:13:16 +1000 Subject: [PATCH 286/345] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c5c67268..42f48da60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ with the exception that 0.x versions can break between minor versions. ## [Unreleased] ### Added - Allow customizing HTML attributes for alert title `

      ` tag via `AttributeProvider` +- Support rendering GFM task list items to Markdown ## [0.28.0] - 2026-03-31 ### Added From 6f4673d4c47f56ff1e54e7db42cc92fafb44ec09 Mon Sep 17 00:00:00 2001 From: Stuart Parker <2979257+stupar73@users.noreply.github.com> Date: Mon, 11 May 2026 14:13:53 +1000 Subject: [PATCH 287/345] Add support for Markdown rendering for YAML front matter --- .../matter/YamlFrontMatterExtension.java | 23 +++- .../YamlFrontMatterMarkdownNodeRenderer.java | 116 ++++++++++++++++++ .../internal/YamlFrontMatterNodeRenderer.java | 13 ++ 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterMarkdownNodeRenderer.java create mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterNodeRenderer.java diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java index 7a2c9f9f5..99ca17708 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java @@ -1,9 +1,15 @@ package org.commonmark.ext.front.matter; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.ext.front.matter.internal.YamlFrontMatterBlockParser; +import org.commonmark.ext.front.matter.internal.YamlFrontMatterMarkdownNodeRenderer; import org.commonmark.parser.Parser; +import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; +import org.commonmark.renderer.markdown.MarkdownRenderer; /** * Extension for YAML-like metadata. @@ -16,7 +22,7 @@ * The parsed metadata is turned into {@link YamlFrontMatterNode}. You can access the metadata using {@link YamlFrontMatterVisitor}. *

      */ -public class YamlFrontMatterExtension implements Parser.ParserExtension { +public class YamlFrontMatterExtension implements Parser.ParserExtension, MarkdownRenderer.MarkdownRendererExtension { private YamlFrontMatterExtension() { } @@ -29,4 +35,19 @@ public void extend(Parser.Builder parserBuilder) { public static Extension create() { return new YamlFrontMatterExtension(); } + + @Override + public void extend(MarkdownRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new YamlFrontMatterMarkdownNodeRenderer(context); + } + + @Override + public Set getSpecialCharacters() { + return Set.of(); + } + }); + } } diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterMarkdownNodeRenderer.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterMarkdownNodeRenderer.java new file mode 100644 index 000000000..355596c8e --- /dev/null +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterMarkdownNodeRenderer.java @@ -0,0 +1,116 @@ +package org.commonmark.ext.front.matter.internal; + +import java.util.List; +import org.commonmark.ext.front.matter.YamlFrontMatterNode; +import org.commonmark.node.Node; +import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; +import org.commonmark.renderer.markdown.MarkdownWriter; + +public class YamlFrontMatterMarkdownNodeRenderer extends YamlFrontMatterNodeRenderer { + + private final MarkdownWriter writer; + + public YamlFrontMatterMarkdownNodeRenderer(MarkdownNodeRendererContext context) { + this.writer = context.getWriter(); + } + + @Override + public void render(Node node) { + renderBoundary(); + Node child = node.getFirstChild(); + while (child != null) { + if (child instanceof YamlFrontMatterNode) { + renderNode((YamlFrontMatterNode) child); + } + child = child.getNext(); + } + renderBoundary(); + writer.line(); + } + + private void renderBoundary() { + writer.raw("---"); + writer.line(); + } + + private void renderNode(YamlFrontMatterNode node) { + var values = node.getValues(); + if (values.isEmpty()) { + renderEmptyValue(node.getKey()); + } else if (values.size() == 1) { + var value = values.get(0); + if (value.contains("\n")) { + renderMultiLineValue(node.getKey(), value.split("\n")); + } else { + renderSingleValue(node.getKey(), value); + } + } else { + renderListValue(node.getKey(), values); + } + } + + private void renderEmptyValue(String key) { + writer.raw(key + ":"); + writer.line(); + } + + private void renderSingleValue(String key, String value) { + writer.raw(key + ": " + escapeValue(value)); + writer.line(); + } + + private void renderMultiLineValue(String key, String[] lines) { + writer.raw(key + ": |"); + writer.line(); + for (var line : lines) { + writer.raw(" " + line); + writer.line(); + } + } + + private void renderListValue(String key, List values) { + writer.raw(key + ":"); + writer.line(); + for (var value : values) { + writer.raw(" - " + escapeValue(value)); + writer.line(); + } + } + + private String escapeValue(String value) { + if (needsQuoting(value)) { + return "'" + value.replace("'", "''") + "'"; + } + return value; + } + + private boolean needsQuoting(String value) { + /* + * NOTE: Deliberately not escaping values which are balanced flow-style arrays/mappings. + * This preserves the round-trip behaviour where these are parsed as a plain string - outputting them as-is will + * result in a valid flow-style array/mapping in the output. + */ + if (isFlowCollection(value)) { + return false; + } + + return value.isEmpty() + // Key/value separator + || value.contains(": ") + // Comment indicator + || value.contains(" #") + // List indicator + || value.startsWith("-") + || value.contains("'") + || value.contains("\"") + // Unbalanced flow-style list + || value.startsWith("[") + // Unbalanced flow-style mapping + || value.startsWith("{"); + } + + private boolean isFlowCollection(String value) { + return (value.startsWith("[") && value.endsWith("]")) + || (value.startsWith("{") && value.endsWith("}")); + } +} diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterNodeRenderer.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterNodeRenderer.java new file mode 100644 index 000000000..607b621ec --- /dev/null +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterNodeRenderer.java @@ -0,0 +1,13 @@ +package org.commonmark.ext.front.matter.internal; + +import java.util.Set; +import org.commonmark.ext.front.matter.YamlFrontMatterBlock; +import org.commonmark.node.Node; +import org.commonmark.renderer.NodeRenderer; + +abstract class YamlFrontMatterNodeRenderer implements NodeRenderer { + @Override + public Set> getNodeTypes() { + return Set.of(YamlFrontMatterBlock.class); + } +} From 85dd241553108070aeb6327fbeed413db02fb78e Mon Sep 17 00:00:00 2001 From: Stuart Parker <2979257+stupar73@users.noreply.github.com> Date: Mon, 11 May 2026 14:13:54 +1000 Subject: [PATCH 288/345] Add tests --- .../YamlFrontMatterMarkdownRendererTest.java | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterMarkdownRendererTest.java diff --git a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterMarkdownRendererTest.java b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterMarkdownRendererTest.java new file mode 100644 index 000000000..987513ea4 --- /dev/null +++ b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterMarkdownRendererTest.java @@ -0,0 +1,199 @@ +package org.commonmark.ext.front.matter; + +import org.commonmark.Extension; +import org.commonmark.node.Document; +import org.commonmark.node.Node; +import org.commonmark.node.Paragraph; +import org.commonmark.node.Text; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.markdown.MarkdownRenderer; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class YamlFrontMatterMarkdownRendererTest { + + private static final List EXTENSIONS = List.of(YamlFrontMatterExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + + // ===== Round-trip tests (parse string -> render -> compare to input) ===== + + @Test + public void testRoundTripSimple() { + assertRoundTrip("---\ntitle: My Document\n---\n\nMarkdown content\n"); + } + + @Test + public void testRoundTripEmptyValue() { + assertRoundTrip("---\nkey:\n---\n\nMarkdown content\n"); + } + + @Test + public void testRoundTripMultipleKeys() { + assertRoundTrip("---\ntitle: My Document\nauthor: John Doe\n---\n\nMarkdown content\n"); + } + + @Test + public void testRoundTripListValues() { + assertRoundTrip("---\ntags:\n - java\n - markdown\n---\n\nMarkdown content\n"); + } + + @Test + public void testRoundTripLiteralBlock() { + assertRoundTrip("---\ndescription: |\n first line\n second line\n---\n\nMarkdown content\n"); + } + + @Test + public void testRoundTripSingleQuotedValue() { + assertRoundTrip("---\nkey: 'value with ''single quotes'''\n---\n\nMarkdown content\n"); + } + + @Test + public void testRoundTripDoubleQuotedValue() { + /* + * NOTE: We don't know what the original escape character was and the markdown renderer always uses single + * quote, hence why this technically doesn't round-trip. + */ + var input = "---\nkey: \"value with \\\"double quotes\\\"\"\n---\n\nMarkdown content\n"; + var rendered = RENDERER.render(PARSER.parse(input)); + var expected = "---\nkey: 'value with \"double quotes\"'\n---\n\nMarkdown content\n"; + assertThat(rendered).isEqualTo(expected); + } + + @Test + public void testRoundTripFlowList() { + // Flow-style list is stored as a single value - "[java, markdown]" - rendered back unquoted + assertRoundTrip("---\ntags: [java, markdown]\n---\n\nMarkdown content\n"); + } + + @Test + public void testRoundTripFlowMapping() { + // Flow-style mapping is stored as a single value - "{key: value}" - rendered back unquoted + assertRoundTrip("---\ndata: {key: value}\n---\n\nMarkdown content\n"); + } + + @Test + public void testRoundTripEmptyFrontmatter() { + assertRoundTrip("---\n---\n\nMarkdown content\n"); + } + + // ===== Programmatic construction tests ===== + + @Test + public void testProgrammaticallyBuilt() { + var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("title", List.of("My Document")))); + + assertRenderedEquals(doc, "---\ntitle: My Document\n---\n\nMarkdown content\n"); + } + + // ===== Quoting tests (values needing special treatment) ===== + + @Test + public void testValueWithColonSpace() { + var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("value with a: colon inside")))); + + assertRenderedEquals(doc, "---\nkey: 'value with a: colon inside'\n---\n\nMarkdown content\n"); + } + + @Test + public void testValueWithColonNoSpace() { + // Colon without trailing space is fine unquoted (e.g. timestamps, URLs) + var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("time", List.of("12:30:00")))); + + assertRenderedEquals(doc, "---\ntime: 12:30:00\n---\n\nMarkdown content\n"); + } + + @Test + public void testValueStartingWithDash() { + var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("- not a list")))); + + assertRenderedEquals(doc, "---\nkey: '- not a list'\n---\n\nMarkdown content\n"); + } + + @Test + public void testValueStartingWithUnmatchedBracket() { + var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("[broken")))); + + assertRenderedEquals(doc, "---\nkey: '[broken'\n---\n\nMarkdown content\n"); + } + + @Test + public void testValueStartingWithMatchedBrackets() { + // Valid flow list - should NOT be quoted + var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("flowList", List.of("[1, 2, 3]")))); + + assertRenderedEquals(doc, "---\nflowList: [1, 2, 3]\n---\n\nMarkdown content\n"); + } + + @Test + public void testValueStartingWithUnmatchedBrace() { + var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("{broken")))); + + assertRenderedEquals(doc, "---\nkey: '{broken'\n---\n\nMarkdown content\n"); + } + + @Test + public void testValueStartingWithMatchedBraces() { + // Valid flow mapping - should NOT be quoted + var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("flowMapping", List.of("{key: val}")))); + + assertRenderedEquals(doc, "---\nflowMapping: {key: val}\n---\n\nMarkdown content\n"); + } + + @Test + public void testValueContainingHashComment() { + var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("value # not a comment")))); + + assertRenderedEquals(doc, "---\nkey: 'value # not a comment'\n---\n\nMarkdown content\n"); + } + + @Test + public void testValueContainingApostrophe() { + var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("it's a test")))); + + assertRenderedEquals(doc, "---\nkey: 'it''s a test'\n---\n\nMarkdown content\n"); + } + + @Test + public void testEmptyStringValue() { + var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("empty", List.of("")))); + + assertRenderedEquals(doc, "---\nempty: ''\n---\n\nMarkdown content\n"); + } + + @Test + public void testValueStartingWithDoubleQuote() { + var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("\"quotes within value\"")))); + + assertRenderedEquals(doc, "---\nkey: '\"quotes within value\"'\n---\n\nMarkdown content\n"); + } + + private void assertRoundTrip(String input) { + String rendered = RENDERER.render(PARSER.parse(input)); + assertThat(rendered).isEqualTo(input); + } + + private void assertRenderedEquals(Node inputNode, String expectedOutput) { + var renderedOutput = RENDERER.render(inputNode); + assertThat(renderedOutput).isEqualTo(expectedOutput); + } + + private Document buildDocumentWithFrontMatter(List frontMatterNodes) { + var doc = new Document(); + + var frontmatter = new YamlFrontMatterBlock(); + for (var frontMatterNode : frontMatterNodes) { + frontmatter.appendChild(frontMatterNode); + } + doc.appendChild(frontmatter); + + var para = new Paragraph(); + para.appendChild(new Text("Markdown content")); + doc.appendChild(para); + + return doc; + } +} From 79864a4aa59f3bc9d8108541f1cc98784a2575b8 Mon Sep 17 00:00:00 2001 From: Stuart Parker <2979257+stupar73@users.noreply.github.com> Date: Mon, 11 May 2026 14:13:54 +1000 Subject: [PATCH 289/345] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c5c67268..e7eb1ab39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ with the exception that 0.x versions can break between minor versions. ## [Unreleased] ### Added - Allow customizing HTML attributes for alert title `

      ` tag via `AttributeProvider` +- Support rendering YAML front matter to Markdown ## [0.28.0] - 2026-03-31 ### Added From 63b623a4464c3d4cfcb86b73b6dcb0b6e4a121be Mon Sep 17 00:00:00 2001 From: Stuart Parker <2979257+stupar73@users.noreply.github.com> Date: Mon, 11 May 2026 15:36:21 +1000 Subject: [PATCH 290/345] Replace unnecessary LHS type with `var` Co-authored-by: Robin Stocker --- .../list/items/internal/TaskListItemMarkdownNodeRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemMarkdownNodeRenderer.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemMarkdownNodeRenderer.java index 9910de893..d2b363952 100644 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemMarkdownNodeRenderer.java +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemMarkdownNodeRenderer.java @@ -18,7 +18,7 @@ public TaskListItemMarkdownNodeRenderer(MarkdownNodeRendererContext context) { @Override public void render(Node node) { if (node instanceof TaskListItemMarker) { - TaskListItemMarker taskListItemNode = (TaskListItemMarker) node; + var taskListItemNode = (TaskListItemMarker) node; var checkboxFill = taskListItemNode.isChecked() ? "x" : " "; writer.raw("[" + checkboxFill + "] "); renderChildren(node); From d2df7f6763ddcf8b87a08c813cdecc5a410856f9 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Sun, 3 May 2026 03:51:21 -0700 Subject: [PATCH 291/345] Add Support for Custom Alert Titles --- CHANGELOG.md | 19 +- README.md | 19 +- commonmark-ext-gfm-alerts/README.md | 52 +++- .../org/commonmark/ext/gfm/alerts/Alert.java | 2 + .../commonmark/ext/gfm/alerts/AlertTitle.java | 25 ++ .../ext/gfm/alerts/AlertsExtension.java | 63 +++- .../gfm/alerts/internal/AlertBlockParser.java | 243 +++++++++++++++ .../internal/AlertHtmlNodeRenderer.java | 14 +- .../internal/AlertMarkdownNodeRenderer.java | 15 +- .../alerts/internal/AlertPostProcessor.java | 111 ------- .../alerts/AlertsMarkdownRendererTest.java | 55 +++- .../ext/gfm/alerts/AlertsSpecTest.java | 9 +- .../commonmark/ext/gfm/alerts/AlertsTest.java | 286 +++++++++++++++++- 13 files changed, 767 insertions(+), 146 deletions(-) create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertTitle.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java delete mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java diff --git a/CHANGELOG.md b/CHANGELOG.md index afce72502..254826f18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,15 +8,24 @@ with the exception that 0.x versions can break between minor versions. ## [Unreleased] ### Added -- Allow customizing HTML attributes for alert title `

      ` tag via `AttributeProvider` - Support rendering GFM task list items to Markdown - Support rendering YAML front matter to Markdown +- Alerts + - Allow customizing HTML attributes for alert title `

      ` tag via `AttributeProvider` + - New configuration for `AlertsExtension` to allow authors to provide custom + titles per alert. See the + [custom titles section of the alerts README](./commonmark-ext-gfm-alerts/README.md#custom-alert-titles) + for more information. + - New configuration for `AlertsExtension` to allow alerts to be nested within + other blocks (including other alerts). See + [this section of the alerts README](./commonmark-ext-gfm-alerts/README.md#nesting-alerts) + for more information. ## [0.28.0] - 2026-03-31 ### Added - New extension for alerts (aka callouts/admonitions) - Syntax: - ``` + ```markdown > [!NOTE] > The text of the note. ``` @@ -104,9 +113,9 @@ with the exception that 0.x versions can break between minor versions. ### Added - New extension for footnotes! - Syntax: - ``` + ```markdown Main text[^1] - + [^1]: Additional text in a footnote ``` - Inline footnotes like `^[inline footnote]` are also supported when enabled @@ -271,7 +280,7 @@ with the exception that 0.x versions can break between minor versions. - Use class `ImageAttributesExtension` in artifact `commonmark-ext-image-attributes` - Extension for task lists (GitHub-style), thanks @dohertyfjatl - Syntax: - ``` + ```markdown - [x] task #1 - [ ] task #2 ``` diff --git a/README.md b/README.md index 7bf598ba3..1f00e0f01 100644 --- a/README.md +++ b/README.md @@ -339,21 +339,22 @@ Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. Adds support for GitHub-style alerts (also known as callouts or admonitions) as described [here](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts), e.g.: -``` +```markdown > [!NOTE] > The text of the note. ``` As types you can use NOTE, TIP, IMPORTANT, WARNING, CAUTION; or configure the extension to add additional ones. -Use class `AlertsExtension` in artifact `commonmark-ext-gfm-alerts`. +Use class `AlertsExtension` in artifact `commonmark-ext-gfm-alerts`. See the +[`AlertsExtension` README](./commonmark-ext-gfm-alerts/README.md) for more information. ### Footnotes Enables footnotes like in [GitHub](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#footnotes) or [Pandoc](https://pandoc.org/MANUAL.html#footnotes): -``` +```markdown Main text[^1] [^1]: Additional text in a footnote @@ -370,7 +371,7 @@ is based on the text of the heading. `# Heading` will be rendered as: -``` +```html

      Heading

      ``` @@ -391,7 +392,7 @@ Use class `InsExtension` in artifact `commonmark-ext-ins`. Adds support for metadata through a YAML front matter block. This extension only supports a subset of YAML syntax. Here's an example of what's supported: -``` +```markdown --- key: value list: @@ -414,11 +415,11 @@ Adds support for specifying attributes (specifically height and width) for image The attribute elements are given as `key=value` pairs inside curly braces `{ }` after the image node to which they apply, for example: -``` +```markdown ![text](/url.png){width=640 height=480} ``` will be rendered as: -``` +```html text ``` @@ -436,12 +437,12 @@ whitespace character or the letter `x` in lowercase or uppercase, then a right b whitespace before any other content. For example: -``` +```markdown - [ ] task #1 - [x] task #2 ``` will be rendered as: -``` +```html
      • task #1
      • task #2
      • diff --git a/commonmark-ext-gfm-alerts/README.md b/commonmark-ext-gfm-alerts/README.md index 2368812e5..9675a72a7 100644 --- a/commonmark-ext-gfm-alerts/README.md +++ b/commonmark-ext-gfm-alerts/README.md @@ -6,7 +6,7 @@ Enables highlighting important information using blockquote syntax with five sta ## Usage -#### Markdown Syntax +### Markdown Syntax ```markdown > [!NOTE] @@ -16,7 +16,7 @@ Enables highlighting important information using blockquote syntax with five sta > Critical information ``` -#### Standard GFM Types +### Standard GFM Types ```java var extension = AlertsExtension.create(); @@ -24,7 +24,7 @@ var parser = Parser.builder().extensions(List.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); ``` -#### Custom Alert Types +### Custom Alert Types Add custom types beyond the five standard GFM types: @@ -36,7 +36,45 @@ var extension = AlertsExtension.builder() Custom types must be UPPERCASE. Standard type titles can also be overridden for localization. -#### Styling +### Custom Alert Titles + +Allow authors to provide custom titles per alert by adding text after the alert +marker on the same line: + +```java +var extension = AlertsExtension.builder().allowCustomTitles().build(); +``` + +```markdown +> [!NOTE] Keep in mind +> Useful information + +> [!WARNING] Be **very** careful +> Critical information +``` + +### Nesting Alerts + +By default, alerts cannot be nested within other blocks. Alerts within other +blocks are parsed as regular block quotes. + +```markdown + +> [!NOTE] +> Useful information + + +- > [!NOTE] + > Useful information +``` + +This behavior can be changed to allow nested alerts: + +```java +var extension = AlertsExtension.builder().allowNestedAlerts().build(); +``` + +### Styling Alerts render as `
        ` elements with CSS classes: @@ -51,9 +89,9 @@ Basic CSS example: ```css .markdown-alert { - padding: 0.5rem 1rem; - margin-bottom: 1rem; - border-left: 4px solid; + padding: 0.5rem 1rem; + margin-bottom: 1rem; + border-left: 4px solid; } .markdown-alert-note { border-color: #0969da; background-color: #ddf4ff; } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java index bb28e7344..b95f697ed 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java @@ -4,6 +4,8 @@ /** * Alert block for highlighting important information using {@code [!TYPE]} syntax. + * + * @see AlertTitle */ public class Alert extends CustomBlock { diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertTitle.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertTitle.java new file mode 100644 index 000000000..35fbcd43f --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertTitle.java @@ -0,0 +1,25 @@ +package org.commonmark.ext.gfm.alerts; + +import org.commonmark.node.CustomNode; + +/** + * Inline content container for the optional custom title of an {@link Alert}. + * + *

        + * When present, an {@code AlertTitle} is always the first child of an {@link Alert}. + * Its own children are the parsed inline nodes of the title (i.e., the text after + * the {@code [!TYPE]} marker on the same line). For example, in + * + *

        {@code
        + * > [!NOTE] Custom _title_
        + * > Body text
        + * }
        + * + * the {@code AlertTitle} contains a {@code Text} node ({@code "Custom "}) followed + * by an {@code Emphasis} node wrapping {@code "title"}. + * + * @see AlertsExtension.Builder#allowCustomTitles() + * @see AlertsExtension.Builder#disallowCustomTitles() + */ +public class AlertTitle extends CustomNode { +} diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index b9a449854..a3c1f3b3d 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -1,7 +1,7 @@ package org.commonmark.ext.gfm.alerts; import org.commonmark.Extension; -import org.commonmark.ext.gfm.alerts.internal.AlertPostProcessor; +import org.commonmark.ext.gfm.alerts.internal.AlertBlockParser; import org.commonmark.ext.gfm.alerts.internal.AlertHtmlNodeRenderer; import org.commonmark.ext.gfm.alerts.internal.AlertMarkdownNodeRenderer; import org.commonmark.parser.Parser; @@ -24,6 +24,17 @@ * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, * {@link HtmlRenderer.Builder#extensions(Iterable)}). * Parsed alerts become {@link Alert} blocks. + * + * The {@link #create() default configuration} of this extension will match GFM + * exactly, with the following exceptions: + * + * - Alert markers take precedence over link reference definitions. + * - Lazy continuation is not allowed between the marker and the body text. Example: + * + *
        {@code
        + *   > [!NOTE]
        + *   Lazy body text will be parsed as a new paragraph
        + *   }
        */ public class AlertsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, MarkdownRenderer.MarkdownRendererExtension { @@ -31,9 +42,13 @@ public class AlertsExtension implements Parser.ParserExtension, HtmlRenderer.Htm static final Set STANDARD_TYPES = Set.of("NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION"); private final Map customTypes; + private final boolean customTitlesAllowed; + private final boolean nestedAlertsAllowed; private AlertsExtension(Builder builder) { this.customTypes = new HashMap<>(builder.customTypes); + this.customTitlesAllowed = builder.customTitlesAllowed; + this.nestedAlertsAllowed = builder.nestedAlertsAllowed; } public static Extension create() { @@ -48,7 +63,8 @@ public static Builder builder() { public void extend(Parser.Builder parserBuilder) { var allowedTypes = new HashSet<>(STANDARD_TYPES); allowedTypes.addAll(customTypes.keySet()); - parserBuilder.postProcessor(new AlertPostProcessor(allowedTypes)); + parserBuilder.customBlockParserFactory( + new AlertBlockParser.Factory(allowedTypes, customTitlesAllowed, nestedAlertsAllowed)); } @Override @@ -76,6 +92,8 @@ public Set getSpecialCharacters() { */ public static class Builder { private final Map customTypes = new HashMap<>(); + private boolean customTitlesAllowed = false; + private boolean nestedAlertsAllowed = false; /** * Adds a custom alert type with a display title. @@ -101,6 +119,47 @@ public Builder addCustomType(String type, String title) { return this; } + /** + * Allows custom titles on alerts. See {@link AlertTitle} for more information. + * @return {@code this} + */ + public Builder allowCustomTitles() { + customTitlesAllowed = true; + return this; + } + + /** + * Disallows custom titles on alerts. See {@link AlertTitle} for more information. + * @return {@code this} + */ + public Builder disallowCustomTitles() { + customTitlesAllowed = false; + return this; + } + + /** + * Allows alerts to be parsed within blocks other than {@code Document} (the root). + *

        + * Note that even with this enabled, {@link Parser.Builder#maxOpenBlockParsers(int)} + * will be respected. + * @return {@code this} + */ + public Builder allowNestedAlerts() { + nestedAlertsAllowed = true; + return this; + } + + /** + * Prevents alerts from being parsed within blocks other than {@code Document} + * (the root). If an alert appears within another block, it will be parsed as + * a regular {@code BlockQuote}. + * @return {@code this} + */ + public Builder disallowNestedAlerts() { + nestedAlertsAllowed = false; + return this; + } + /** * @return a configured {@link Extension} */ diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java new file mode 100644 index 000000000..d062a1a39 --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java @@ -0,0 +1,243 @@ +package org.commonmark.ext.gfm.alerts.internal; + +import java.util.Locale; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.commonmark.ext.gfm.alerts.Alert; +import org.commonmark.ext.gfm.alerts.AlertTitle; +import org.commonmark.node.Block; +import org.commonmark.node.BlockQuote; +import org.commonmark.node.Document; +import org.commonmark.node.Paragraph; +import org.commonmark.parser.InlineParser; +import org.commonmark.parser.SourceLine; +import org.commonmark.parser.SourceLines; +import org.commonmark.parser.block.AbstractBlockParser; +import org.commonmark.parser.block.AbstractBlockParserFactory; +import org.commonmark.parser.block.BlockContinue; +import org.commonmark.parser.block.BlockStart; +import org.commonmark.parser.block.MatchedBlockParser; +import org.commonmark.parser.block.ParserState; +import org.commonmark.text.Characters; + +public class AlertBlockParser extends AbstractBlockParser { + + private static final Pattern ALERT_PATTERN_NO_CUSTOM_TITLE = Pattern.compile("^\\[!([a-zA-Z]+)]\\s*$"); + private static final Pattern ALERT_PATTERN_CUSTOM_TITLE = Pattern.compile("^\\[!([a-zA-Z]+)](.*)$"); + + private final Alert block; + private final String typeOriginalCase; + private final String titleContent; + + private AlertBlockParser(String type, String typeOriginalCase, String titleContent) { + this.block = new Alert(type); + this.typeOriginalCase = typeOriginalCase; + this.titleContent = titleContent; + } + + @Override + public Block getBlock() { + return block; + } + + @Override + public boolean isContainer() { + return true; + } + + @Override + public boolean canContain(Block childBlock) { + return true; + } + + @Override + public BlockContinue tryContinue(ParserState state) { + /* + * Same continuation rule as a block quote: line must start with '>' + * (with up to 3 leading spaces, optional space after '>') + */ + var line = state.getLine().getContent(); + int nextNonSpace = state.getNextNonSpaceIndex(); + if (state.getIndent() >= 4 // Parsing.CODE_BLOCK_INDENT + || nextNonSpace >= line.length() + || line.charAt(nextNonSpace) != '>') { + return BlockContinue.none(); + } + + int newColumn = state.getColumn() + state.getIndent() + 1; + if (Characters.isSpaceOrTab(line, nextNonSpace + 1)) { + newColumn++; + } + + return BlockContinue.atColumn(newColumn); + } + + @Override + public void parseInlines(InlineParser inlineParser) { + // Determine if there is any non-title body content. + if (block.getFirstChild() == null) { + /* + * Replace the Alert with a BlockQuote whose only paragraph contains + * the original first line text. + */ + demoteToBlockQuote(inlineParser); + return; + } + + if (titleContent.isEmpty()) { + return; + } + + /* + * Inline-parse the title in its own scope so delimiters are isolated + * from the body text. For example: + * + * > [!NOTE] 2*2 = 4 + * > But 3*3 = 9 + */ + var titleNode = new AlertTitle(); + inlineParser.parse(SourceLines.of(SourceLine.of(titleContent, null)), titleNode); + + // Body blocks were attached as children during block parsing. Prepend the title. + block.prependChild(titleNode); + } + + private void demoteToBlockQuote(InlineParser inlineParser) { + var bq = new BlockQuote(); + bq.setSourceSpans(block.getSourceSpans()); + var p = new Paragraph(); + + // Build the literal text including the alert marker and title. + var literal = "[!" + typeOriginalCase + "]"; + if (!titleContent.isEmpty()) { + /* + * This may not preserve the original number of spaces between the + * alert marker and title (e.g., if there were 0 or 2+ spaces). + */ + literal += " " + titleContent; + } + + // Parse the inlines of the full content (alert marker + title) + inlineParser.parse(SourceLines.of(SourceLine.of(literal, null)), p); + bq.appendChild(p); + block.insertAfter(bq); + block.unlink(); + } + + public static class Factory extends AbstractBlockParserFactory { + + private final Set allowedTypes; + private final boolean customTitlesAllowed; + private final boolean nestedAlertsAllowed; + + public Factory(Set allowedTypes, boolean customTitlesAllowed, boolean nestedAlertsAllowed) { + this.allowedTypes = allowedTypes; + this.customTitlesAllowed = customTitlesAllowed; + this.nestedAlertsAllowed = nestedAlertsAllowed; + } + + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + // Parsing.CODE_BLOCK_INDENT + if (state.getIndent() >= 4) { + return BlockStart.none(); + } + + if (!nestedAlertsAllowed && !isAtRoot(matchedBlockParser.getMatchedBlockParser().getBlock())) { + return BlockStart.none(); + } + + var line = state.getLine().getContent(); + int nextNonSpace = state.getNextNonSpaceIndex(); + + // Case A: Fresh start. Line begins with '>'. + if (nextNonSpace < line.length() && line.charAt(nextNonSpace) == '>') { + return tryStartFresh(line, nextNonSpace, state); + } + + /* + * Case B: Promotion. We're already inside a BlockQuote whose body so far is + * empty (only blank '>' lines), and the current line (already stripped of + * its '>' prefix by BlockQuoteParser.tryContinue) is the marker. Replace the + * active block quote with an alert. + */ + var matched = matchedBlockParser.getMatchedBlockParser().getBlock(); + if (matched instanceof BlockQuote && matched.getFirstChild() == null) { + // Null if not a marker. null.replaceActiveBlockParser() would NPE, so guard. + return tryStartFresh(line, nextNonSpace, state); + } + + return BlockStart.none(); + } + + private static boolean isAtRoot(Block matched) { + if (matched instanceof Document) { + return true; + } + + /* + * Case B: Promotion. The matched block is a top-level (Document-parented) + * BlockQuote that's still empty. + */ + if (matched instanceof BlockQuote + && matched.getFirstChild() == null + && matched.getParent() instanceof Document) { + return true; + } + + return false; + } + + private BlockStart tryStartFresh(CharSequence line, int nextNonSpace, ParserState state) { + int afterGt; + if (nextNonSpace < line.length() && line.charAt(nextNonSpace) == '>') { + afterGt = nextNonSpace + 1; + if (Characters.isSpaceOrTab(line, afterGt)) { + afterGt++; + } + } else { + /* + * Promotion path: the '>' has already been consumed by the active + * block quote's tryContinue, so state.getIndex() points past it. + */ + afterGt = state.getIndex(); + } + + Matcher matcher; + if (customTitlesAllowed) { + matcher = ALERT_PATTERN_CUSTOM_TITLE.matcher(line.subSequence(afterGt, line.length())); + } else { + matcher = ALERT_PATTERN_NO_CUSTOM_TITLE.matcher(line.subSequence(afterGt, line.length())); + } + + if (!matcher.matches()) { + return BlockStart.none(); + } + + String typeOriginalCase = matcher.group(1); + String type = typeOriginalCase.toUpperCase(Locale.ROOT); + if (!allowedTypes.contains(type)) { + return BlockStart.none(); + } + + String titleContent = ""; + if (customTitlesAllowed) { + titleContent = matcher.group(2).replaceFirst("^[ \\t]+", "").stripTrailing(); + } + + // Consume the rest of the first line. + var start = BlockStart.of(new AlertBlockParser(type, typeOriginalCase, titleContent)).atIndex(line.length()); + + // If we got here via the promotion path, replace the empty BlockQuote. + var matched = state.getActiveBlockParser().getBlock(); + if (matched instanceof BlockQuote && matched.getFirstChild() == null + && (nextNonSpace >= line.length() || line.charAt(nextNonSpace) != '>')) { + start = start.replaceActiveBlockParser(); + } + + return start; + } + } +} diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java index ca562ba33..af9fff704 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -1,6 +1,7 @@ package org.commonmark.ext.gfm.alerts.internal; import org.commonmark.ext.gfm.alerts.Alert; +import org.commonmark.ext.gfm.alerts.AlertTitle; import org.commonmark.node.Node; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; @@ -35,7 +36,12 @@ protected void renderAlert(Alert alert) { // Render alert title htmlWriter.tag("p", context.extendAttributes(alert, "p", Map.of("class", "markdown-alert-title"))); - htmlWriter.text(getAlertTitle(type)); + var first = alert.getFirstChild(); + if (first instanceof AlertTitle) { + renderChildren(first); + } else { + htmlWriter.text(getAlertTitle(type)); + } htmlWriter.tag("/p"); htmlWriter.line(); @@ -71,7 +77,11 @@ private void renderChildren(Node parent) { var node = parent.getFirstChild(); while (node != null) { var next = node.getNext(); - context.render(node); + + // AlertTitle is rendered separately from other nodes. + if (!(node instanceof AlertTitle)) { + context.render(node); + } node = next; } } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java index e3da62aea..ab2504cef 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java @@ -1,6 +1,7 @@ package org.commonmark.ext.gfm.alerts.internal; import org.commonmark.ext.gfm.alerts.Alert; +import org.commonmark.ext.gfm.alerts.AlertTitle; import org.commonmark.node.Node; import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; import org.commonmark.renderer.markdown.MarkdownWriter; @@ -21,6 +22,14 @@ protected void renderAlert(Alert alert) { writer.writePrefix("> "); writer.pushPrefix("> "); writer.raw("[!" + alert.getType() + "]"); + + // Custom title if present, also on the first line. + var first = alert.getFirstChild(); + if (first instanceof AlertTitle) { + writer.raw(" "); + renderChildren(first); + } + writer.line(); renderChildren(alert); writer.popPrefix(); @@ -31,7 +40,11 @@ private void renderChildren(Node parent) { var node = parent.getFirstChild(); while (node != null) { var next = node.getNext(); - context.render(node); + + // AlertTitle is rendered separately from other nodes. + if (!(node instanceof AlertTitle)) { + context.render(node); + } node = next; } } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java deleted file mode 100644 index 8008fc8dd..000000000 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java +++ /dev/null @@ -1,111 +0,0 @@ -package org.commonmark.ext.gfm.alerts.internal; - -import org.commonmark.ext.gfm.alerts.Alert; -import org.commonmark.node.BlockQuote; -import org.commonmark.node.HardLineBreak; -import org.commonmark.node.Node; -import org.commonmark.node.Paragraph; -import org.commonmark.node.SoftLineBreak; -import org.commonmark.node.Text; -import org.commonmark.parser.PostProcessor; - -import java.util.Locale; -import java.util.Set; -import java.util.regex.Pattern; - -public class AlertPostProcessor implements PostProcessor { - - // Alert type marker, matching any case (GitHub supports lowercase, mixed, and uppercase) - private static final Pattern ALERT_PATTERN = Pattern.compile("^\\[!([a-zA-Z]+)]\\s*$"); - - private final Set allowedTypes; - - public AlertPostProcessor(Set allowedTypes) { - this.allowedTypes = allowedTypes; - } - - @Override - public Node process(Node document) { - // Only look at direct children of Document — GitHub only detects alerts at the top level. - var child = document.getFirstChild(); - while (child != null) { - var next = child.getNext(); - if (child instanceof BlockQuote) { - tryConvertToAlert((BlockQuote) child); - } - child = next; - } - return document; - } - - private void tryConvertToAlert(BlockQuote blockQuote) { - var firstChild = blockQuote.getFirstChild(); - if (!(firstChild instanceof Paragraph)) { - return; - } - - var paragraph = (Paragraph) firstChild; - var firstInline = paragraph.getFirstChild(); - if (!(firstInline instanceof Text)) { - return; - } - - var textNode = (Text) firstInline; - - // The alert marker can be the entire text node content, or just the first line - // before a line break (trailing spaces create a HardLineBreak instead of SoftLineBreak). - var afterMarker = firstInline.getNext(); - if (afterMarker != null && !(afterMarker instanceof SoftLineBreak) && !(afterMarker instanceof HardLineBreak)) { - // Text followed by something other than a line break - not an alert - return; - } - - var matcher = ALERT_PATTERN.matcher(textNode.getLiteral()); - if (!matcher.matches()) { - return; - } - - var type = matcher.group(1).toUpperCase(Locale.ROOT); - if (!allowedTypes.contains(type)) { - return; - } - - // Must have content after the marker line. An alert with ONLY the marker - // and no content is a normal blockquote on GitHub. - if (afterMarker != null) { - // There's a line break after marker - check if there's content after it - if (afterMarker.getNext() == null && paragraph.getNext() == null) { - return; - } - afterMarker.unlink(); - } else { - // Marker is the only thing in the paragraph - if (paragraph.getNext() == null) { - return; - } - } - - // Valid alert. Create Alert node and transfer children. - var alert = new Alert(type); - alert.setSourceSpans(blockQuote.getSourceSpans()); - blockQuote.insertAfter(alert); - - // Remove the marker text from the first paragraph - firstInline.unlink(); - - // If paragraph is now empty, remove it - if (paragraph.getFirstChild() == null) { - paragraph.unlink(); - } - - // Move remaining children from blockquote to alert - var child = blockQuote.getFirstChild(); - while (child != null) { - var next = child.getNext(); - alert.appendChild(child); - child = next; - } - - blockQuote.unlink(); - } -} diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java index aca90e2df..0e55ad07a 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java @@ -15,6 +15,14 @@ public class AlertsMarkdownRendererTest { private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + private static final Set EXTENSIONS_CUSTOM_TITLES = Set.of(AlertsExtension.builder().allowCustomTitles().build()); + private static final Parser PARSER_CUSTOM_TITLES = Parser.builder() + .extensions(EXTENSIONS_CUSTOM_TITLES) + .build(); + private static final MarkdownRenderer RENDERER_CUSTOM_TITLES = MarkdownRenderer.builder() + .extensions(EXTENSIONS_CUSTOM_TITLES) + .build(); + @Test public void alertRoundTrip() { assertRoundTrip("> [!WARNING]\n> Be careful\n"); @@ -36,6 +44,12 @@ public void lowercaseTypeRendersAsUppercase() { assertThat(rendered).isEqualTo("> [!NOTE]\n> Content\n"); } + @Test + public void leadingAndTrailingLinesAreRemoved() { + String rendered = RENDERER.render(PARSER.parse(">\n> \n>[!NOTE]\n> Content\n>\n> \n")); + assertThat(rendered).isEqualTo("> [!NOTE]\n> Content\n"); + } + @Test public void alertWithMultipleParagraphs() { String input = "> [!NOTE]\n> First paragraph\n>\n> Second paragraph\n"; @@ -53,21 +67,50 @@ public void customTypeRoundTrip() { Parser parser = Parser.builder().extensions(Set.of(extension)).build(); MarkdownRenderer renderer = MarkdownRenderer.builder().extensions(Set.of(extension)).build(); - String input = "> [!INFO]\n> Custom type\n"; - String rendered = renderer.render(parser.parse(input)); - assertThat(rendered).isEqualTo(input); + + assertRoundTrip(input, parser, renderer); } @Test public void alertWithList() { String input = "> [!NOTE]\n> Items:\n> \n> - First\n> - Second\n"; - String rendered = RENDERER.render(PARSER.parse(input)); + assertRoundTrip(input); + } + + // Custom titles + + @Test + public void customTitleRoundTrip() { + assertRoundTripCustomTitles("> [!WARNING] Custom title\n> Be careful\n"); + } + + @Test + public void customTitleWithFormattingRoundTrip() { + assertRoundTripCustomTitles("> [!WARNING] Custom _title **with `formatting`**_\n> Be careful\n"); + } + + @Test + public void customTitleWithMultipleBlocks() { + String input = "> [!NOTE]Title\n> First paragraph\n>\n> Second paragraph\n>\n> - > Nested blocks\n"; + // MarkdownWriter always writes the prefix including trailing space + String expected = "> [!NOTE] Title\n> First paragraph\n> \n> Second paragraph\n> \n> - > Nested blocks\n"; + String rendered = RENDERER_CUSTOM_TITLES.render(PARSER_CUSTOM_TITLES.parse(input)); + assertThat(rendered).isEqualTo(expected); + } + + // Helpers + + private void assertRoundTrip(String input, Parser parser, MarkdownRenderer renderer) { + String rendered = renderer.render(parser.parse(input)); assertThat(rendered).isEqualTo(input); } private void assertRoundTrip(String input) { - String rendered = RENDERER.render(PARSER.parse(input)); - assertThat(rendered).isEqualTo(input); + assertRoundTrip(input, PARSER, RENDERER); + } + + private void assertRoundTripCustomTitles(String input) { + assertRoundTrip(input, PARSER_CUSTOM_TITLES, RENDERER_CUSTOM_TITLES); } } diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java index 8155d8009..d85aa1d2a 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java @@ -15,6 +15,13 @@ import java.util.List; import java.util.Set; +/** + * Tests the default configuration of {@link AlertsExtension} against how + * GitHub Flavored Markdown (GFM) parses and renders {@link Alert}s. + *

        + * This test should only be used for the default configuration of + * {@link AlertsExtension}. Other configurations cause deviation from GFM. + */ @ParameterizedClass @MethodSource("data") public class AlertsSpecTest extends RenderingTestCase { @@ -41,4 +48,4 @@ public void testHtmlRendering() { protected String render(String source) { return RENDERER.render(PARSER.parse(source)); } -} \ No newline at end of file +} diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java index c46c532fe..f812d3f8b 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -4,6 +4,7 @@ import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; import java.util.Set; @@ -11,10 +12,28 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -public class AlertsTest { +public class AlertsTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(AlertsExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer HTML_RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + + private static final Set EXTENSIONS_CUSTOM_TITLES = Set.of(AlertsExtension.builder().allowCustomTitles().build()); + private static final Parser PARSER_CUSTOM_TITLES = Parser.builder() + .extensions(EXTENSIONS_CUSTOM_TITLES) + .build(); + private static final HtmlRenderer HTML_RENDERER_CUSTOM_TITLES = HtmlRenderer.builder() + .extensions(EXTENSIONS_CUSTOM_TITLES) + .build(); + + @Override + protected String render(String source) { + return HTML_RENDERER.render(PARSER.parse(source)); + } + + private void assertRenderingCustomTitles(String source, String expectedResult) { + assertThat(HTML_RENDERER_CUSTOM_TITLES.render(PARSER_CUSTOM_TITLES.parse(source))).isEqualTo(expectedResult); + } // Custom types @@ -92,6 +111,269 @@ public void overrideStandardTypeTitle() { "

        \n"); } + // Custom titles + + @Test + public void customTitle() { + assertRenderingCustomTitles("> [!NOTE] Custom title\n> Note with a custom title", + "
        \n" + + "

        Custom title

        \n" + + "

        Note with a custom title

        \n" + + "
        \n"); + } + + @Test + public void customTitleNoSpace() { + assertRenderingCustomTitles("> [!NOTE]Custom title\n> Note with a custom title", + "
        \n" + + "

        Custom title

        \n" + + "

        Note with a custom title

        \n" + + "
        \n"); + } + + @Test + public void customTitleExtraSpace() { + assertRenderingCustomTitles("> [!NOTE] Custom title \n> Note with a custom title", + "
        \n" + + "

        Custom title

        \n" + + "

        Note with a custom title

        \n" + + "
        \n"); + } + + @Test + public void customTitleNoHardLineBreak() { + assertRenderingCustomTitles("> [!NOTE] Custom title\\\n> Note with a custom title", + "
        \n" + + "

        Custom title\\

        \n" + + "

        Note with a custom title

        \n" + + "
        \n"); + } + + @Test + public void customTitleWithComment() { + assertRenderingCustomTitles("> [!NOTE] Custom title \n> Note with a custom title", + "
        \n" + + "

        Custom title

        \n" + + "

        Note with a custom title

        \n" + + "
        \n"); + } + + @Test + public void customTitleWithOnlyCommentFallsBackToDefaultTitle() { + assertRenderingCustomTitles("> [!TIP] \n> Body text", + "
        \n" + + "

        Tip

        \n" + + "

        Body text

        \n" + + "
        \n"); + } + + @Test + public void customTitleWithInlineFormatting() { + assertRenderingCustomTitles("> [!NOTE] Custom _title with **formatting**_\n> Note with a custom title", + "
        \n" + + "

        Custom title with formatting

        \n" + + "

        Note with a custom title

        \n" + + "
        \n"); + } + + @Test + public void customTitleWithLinkAndCode() { + assertRenderingCustomTitles("> [!IMPORTANT] See [docs](https://example.com) or `run()`\n> Note with a custom title", + "
        \n" + + "

        See docs or run()

        \n" + + "

        Note with a custom title

        \n" + + "
        \n"); + } + + @Test + public void customTitleLeadingEmptyLines() { + assertRenderingCustomTitles(">\n> \n> [!NOTE] Custom **title**\n> Note with a custom title", + "
        \n" + + "

        Custom title

        \n" + + "

        Note with a custom title

        \n" + + "
        \n"); + } + + @Test + public void customTitleNoOverlappingInlines() { + assertRenderingCustomTitles("> [!NOTE] Custom _title with **opening `delimiters\n> Note` with** closing delimiters_", + "
        \n" + + "

        Custom _title with **opening `delimiters

        \n" + + "

        Note` with** closing delimiters_

        \n" + + "
        \n"); + } + + @Test + public void customTitleIgnoresBlockContent() { + assertRenderingCustomTitles("> [!NOTE] ## Custom title looks like an ATX heading\n>But it's not", + "
        \n" + + "

        ## Custom title looks like an ATX heading

        \n" + + "

        But it's not

        \n" + + "
        \n"); + } + + @Test + public void customTitleNoBody() { + // Inlines should be parsed as usual after falling back to a block quote. + assertRenderingCustomTitles("> [!NOTE] Custom _title_\n> \n>\n>", + "
        \n" + + "

        [!NOTE] Custom title

        \n" + + "
        \n"); + } + + @Test + public void customTitleNoBodyNoSpace() { + assertRenderingCustomTitles("> [!NOTE] Custom _title_\n> \n>\n>", + "
        \n" + + "

        [!NOTE] Custom title

        \n" + + "
        \n"); + } + + @Test + public void onlyTrailingWhitespaceIsNotCustomTitle() { + assertRenderingCustomTitles("> [!NOTE] \n> Body text", + "
        \n" + + "

        Note

        \n" + + "

        Body text

        \n" + + "
        \n"); + } + + // Lazy continuation + + @Test + public void noLazyContinuationAfterMarker() { + assertRendering("> [!NOTE]\nBody text", + "
        \n" + + "

        [!NOTE]

        \n" + + "
        \n" + + "

        Body text

        \n"); + } + + @Test + public void noLazyContinuationAfterTitle() { + assertRenderingCustomTitles("> [!NOTE] Custom title\nBody text", + "
        \n" + + "

        [!NOTE] Custom title

        \n" + + "
        \n" + + "

        Body text

        \n"); + } + + // Alert markers take precedence over link reference definitions + + @Test + public void alertTakesPrecedence() { + assertRenderingCustomTitles("> [!NOTE]: https://example.com\n> Body text\n\n[!NOTE]", + "
        \n" + + "

        : https://example.com

        \n" + + "

        Body text

        \n" + + "
        \n" + + "

        [!NOTE]

        \n"); + } + + @Test + public void alertTakesPrecedenceBefore() { + assertRenderingCustomTitles("> [!NOTE]\n> Body text\n\n[!NOTE]: https://example.com", + "
        \n" + + "

        Note

        \n" + + "

        Body text

        \n" + + "
        \n"); + } + + @Test + public void alertTakesPrecedenceAfter() { + assertRenderingCustomTitles("[!NOTE]: https://example.com\n\n> [!NOTE]\n> Body text", + "
        \n" + + "

        Note

        \n" + + "

        Body text

        \n" + + "
        \n"); + } + + // Nested alerts + + @Test + public void noNestedAlertsByDefault() { + assertRendering("> [!TIP]\n> Body\n\n- > [!TIP]\n > Nested body", + "
        \n" + + "

        Tip

        \n" + + "

        Body

        \n" + + "
        \n" + + "
          \n" + + "
        • \n" + + "
          \n" + + "

          [!TIP]\n" + + "Nested body

          \n" + + "
          \n" + + "
        • \n" + + "
        \n"); + } + + @Test + public void noNestedAlertsByDefaultLeadingEmptyLines() { + assertRendering(">\n> \n> [!TIP]\n> Body\n\n- > [!TIP]\n > Nested body", + "
        \n" + + "

        Tip

        \n" + + "

        Body

        \n" + + "
        \n" + + "
          \n" + + "
        • \n" + + "
          \n" + + "

          [!TIP]\n" + + "Nested body

          \n" + + "
          \n" + + "
        • \n" + + "
        \n"); + } + + @Test + public void nestedAlerts() { + Extension extension = AlertsExtension.builder().allowNestedAlerts().build(); + Parser parser = Parser.builder().extensions(Set.of(extension)).build(); + HtmlRenderer renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); + + var source = String.join("\n", + "> [!TIP]", + "> Tip body", + "> > [!IMPORTANT]", + "> > Important body", + "", + "- > [!NOTE]", + " > Nested body", + " >", + " > 1. Ordered list body", + " >", + " > > [!CAUTION]", + " > >", + " > > Deeply nested body"); + var expected = String.join("\n", + "
        ", + "

        Tip

        ", + "

        Tip body

        ", + "
        ", + "

        Important

        ", + "

        Important body

        ", + "
        ", + "
        ", + "
          ", + "
        • ", + "
          ", + "

          Note

          ", + "

          Nested body

          ", + "
            ", + "
          1. ", + "

            Ordered list body

            ", + "
            ", + "

            Caution

            ", + "

            Deeply nested body

            ", + "
            ", + "
          2. ", + "
          ", + "
          ", + "
        • ", + "
        \n"); + + assertThat(renderer.render(parser.parse(source))).isEqualTo(expected); + } + // Custom type validation @Test @@ -137,4 +419,4 @@ public void customTypeParsedAsAlertNode() { assertThat(alert.getType()).isEqualTo("INFO"); } -} \ No newline at end of file +} From 8968d7b579e1acbb57a994d9803b2f3f5bbc6ea9 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Sun, 3 May 2026 04:05:45 -0700 Subject: [PATCH 292/345] Handle Comment-Only Titles --- .../internal/AlertHtmlNodeRenderer.java | 49 +++++++++++++++++++ .../commonmark/ext/gfm/alerts/AlertsTest.java | 11 ++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java index af9fff704..a7d844690 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -2,7 +2,9 @@ import org.commonmark.ext.gfm.alerts.Alert; import org.commonmark.ext.gfm.alerts.AlertTitle; +import org.commonmark.node.HtmlInline; import org.commonmark.node.Node; +import org.commonmark.node.Text; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; @@ -38,6 +40,21 @@ protected void renderAlert(Alert alert) { htmlWriter.tag("p", context.extendAttributes(alert, "p", Map.of("class", "markdown-alert-title"))); var first = alert.getFirstChild(); if (first instanceof AlertTitle) { + /* + * If the alert title only contains HTML comments like this: + * + * > [!TIP] + * > Body text + * + * Then the reader will see a blank title. In this case, it's better + * to render the default title (but keep the comments for accuracy). + * + * These comments will be visible when rendered to other formats like + * Markdown, so this is only relevant for the HTML renderer. + */ + if (isCommentOnlyTitle(first)) { + htmlWriter.text(getAlertTitle(type)); + } renderChildren(first); } else { htmlWriter.text(getAlertTitle(type)); @@ -73,6 +90,38 @@ private String getAlertTitle(String type) { } } + private boolean isCommentOnlyTitle(Node title) { + var node = title.getFirstChild(); + if (node == null) { + return false; + } + while (node != null) { + if (node instanceof HtmlInline) { + if (!isHtmlComment((HtmlInline) node)) { + return false; + } + } else if (node instanceof Text) { + if (!((Text) node).getLiteral().trim().isEmpty()) { + return false; + } + } else { + return false; + } + node = node.getNext(); + } + return true; + } + + private boolean isHtmlComment(HtmlInline htmlInline) { + String literal = htmlInline.getLiteral(); + if (literal == null || !literal.startsWith("") + || literal.equals("") + || literal.endsWith("-->"); + } + private void renderChildren(Node parent) { var node = parent.getFirstChild(); while (node != null) { diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java index f812d3f8b..897a693fb 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -162,7 +162,7 @@ public void customTitleWithComment() { public void customTitleWithOnlyCommentFallsBackToDefaultTitle() { assertRenderingCustomTitles("> [!TIP] \n> Body text", "
        \n" + - "

        Tip

        \n" + + "

        Tip

        \n" + "

        Body text

        \n" + "
        \n"); } @@ -238,6 +238,15 @@ public void onlyTrailingWhitespaceIsNotCustomTitle() { "
    \n"); } + @Test + public void onlyHtmlCommentsInTitleUseDefaultTitle() { + assertRenderingCustomTitles("> [!NOTE] \n> Body text", + "
    \n" + + "

    Note

    \n" + + "

    Body text

    \n" + + "
    \n"); + } + // Lazy continuation @Test From 74f4a8a4b06d22e183c457f26f24e70af2edcf9b Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Sun, 31 May 2026 22:25:29 -0700 Subject: [PATCH 293/345] Address Styling Comments --- commonmark-ext-gfm-alerts/README.md | 4 +- .../ext/gfm/alerts/AlertsExtension.java | 39 ++++++------------- .../gfm/alerts/internal/AlertBlockParser.java | 20 ++++------ .../internal/AlertHtmlNodeRenderer.java | 2 +- .../alerts/AlertsMarkdownRendererTest.java | 2 +- .../commonmark/ext/gfm/alerts/AlertsTest.java | 4 +- 6 files changed, 26 insertions(+), 45 deletions(-) diff --git a/commonmark-ext-gfm-alerts/README.md b/commonmark-ext-gfm-alerts/README.md index 9675a72a7..026408a56 100644 --- a/commonmark-ext-gfm-alerts/README.md +++ b/commonmark-ext-gfm-alerts/README.md @@ -42,7 +42,7 @@ Allow authors to provide custom titles per alert by adding text after the alert marker on the same line: ```java -var extension = AlertsExtension.builder().allowCustomTitles().build(); +var extension = AlertsExtension.builder().allowCustomTitles(true).build(); ``` ```markdown @@ -71,7 +71,7 @@ blocks are parsed as regular block quotes. This behavior can be changed to allow nested alerts: ```java -var extension = AlertsExtension.builder().allowNestedAlerts().build(); +var extension = AlertsExtension.builder().allowNestedAlerts(true).build(); ``` ### Styling diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index a3c1f3b3d..704d8d5cd 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -120,43 +120,28 @@ public Builder addCustomType(String type, String title) { } /** - * Allows custom titles on alerts. See {@link AlertTitle} for more information. + * Allows or disallows custom titles on alerts. See {@link AlertTitle} for more information. + * @param allow Whether to allow or disallow custom titles on alerts. * @return {@code this} */ - public Builder allowCustomTitles() { - customTitlesAllowed = true; + public Builder allowCustomTitles(boolean allow) { + customTitlesAllowed = allow; return this; } /** - * Disallows custom titles on alerts. See {@link AlertTitle} for more information. - * @return {@code this} - */ - public Builder disallowCustomTitles() { - customTitlesAllowed = false; - return this; - } - - /** - * Allows alerts to be parsed within blocks other than {@code Document} (the root). + * Allows or disallows parsing alerts within non-root blocks ({@code Document}). *

    - * Note that even with this enabled, {@link Parser.Builder#maxOpenBlockParsers(int)} - * will be respected. - * @return {@code this} - */ - public Builder allowNestedAlerts() { - nestedAlertsAllowed = true; - return this; - } - - /** - * Prevents alerts from being parsed within blocks other than {@code Document} - * (the root). If an alert appears within another block, it will be parsed as + * When disallowed, if an alert appears within another block, it will be parsed as * a regular {@code BlockQuote}. + *

    + * Note that even when this is allowed, {@link Parser.Builder#maxOpenBlockParsers(int)} + * will be respected. + * @param allow Whether to allow or disallow parsing alerts within non-root blocks. * @return {@code this} */ - public Builder disallowNestedAlerts() { - nestedAlertsAllowed = false; + public Builder allowNestedAlerts(boolean allow) { + nestedAlertsAllowed = allow; return this; } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java index d062a1a39..35607307f 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java @@ -59,14 +59,14 @@ public BlockContinue tryContinue(ParserState state) { * (with up to 3 leading spaces, optional space after '>') */ var line = state.getLine().getContent(); - int nextNonSpace = state.getNextNonSpaceIndex(); + var nextNonSpace = state.getNextNonSpaceIndex(); if (state.getIndent() >= 4 // Parsing.CODE_BLOCK_INDENT || nextNonSpace >= line.length() || line.charAt(nextNonSpace) != '>') { return BlockContinue.none(); } - int newColumn = state.getColumn() + state.getIndent() + 1; + var newColumn = state.getColumn() + state.getIndent() + 1; if (Characters.isSpaceOrTab(line, nextNonSpace + 1)) { newColumn++; } @@ -150,7 +150,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } var line = state.getLine().getContent(); - int nextNonSpace = state.getNextNonSpaceIndex(); + var nextNonSpace = state.getNextNonSpaceIndex(); // Case A: Fresh start. Line begins with '>'. if (nextNonSpace < line.length() && line.charAt(nextNonSpace) == '>') { @@ -205,24 +205,20 @@ private BlockStart tryStartFresh(CharSequence line, int nextNonSpace, ParserStat afterGt = state.getIndex(); } - Matcher matcher; - if (customTitlesAllowed) { - matcher = ALERT_PATTERN_CUSTOM_TITLE.matcher(line.subSequence(afterGt, line.length())); - } else { - matcher = ALERT_PATTERN_NO_CUSTOM_TITLE.matcher(line.subSequence(afterGt, line.length())); - } + var pattern = customTitlesAllowed ? ALERT_PATTERN_CUSTOM_TITLE : ALERT_PATTERN_NO_CUSTOM_TITLE; + var matcher = pattern.matcher(line.subSequence(afterGt, line.length())); if (!matcher.matches()) { return BlockStart.none(); } - String typeOriginalCase = matcher.group(1); - String type = typeOriginalCase.toUpperCase(Locale.ROOT); + var typeOriginalCase = matcher.group(1); + var type = typeOriginalCase.toUpperCase(Locale.ROOT); if (!allowedTypes.contains(type)) { return BlockStart.none(); } - String titleContent = ""; + var titleContent = ""; if (customTitlesAllowed) { titleContent = matcher.group(2).replaceFirst("^[ \\t]+", "").stripTrailing(); } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java index a7d844690..062f637e1 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -113,7 +113,7 @@ private boolean isCommentOnlyTitle(Node title) { } private boolean isHtmlComment(HtmlInline htmlInline) { - String literal = htmlInline.getLiteral(); + var literal = htmlInline.getLiteral(); if (literal == null || !literal.startsWith(" + * > [!NOTE] + * + * + * > [!NOTE] Custom title + * }

    * - Lazy continuation is not allowed between the marker and the body text. Example: * *
    {@code
    diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java
    index 35607307f..fc1cee97b 100644
    --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java
    +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java
    @@ -2,7 +2,6 @@
     
     import java.util.Locale;
     import java.util.Set;
    -import java.util.regex.Matcher;
     import java.util.regex.Pattern;
     
     import org.commonmark.ext.gfm.alerts.Alert;
    @@ -10,7 +9,6 @@
     import org.commonmark.node.Block;
     import org.commonmark.node.BlockQuote;
     import org.commonmark.node.Document;
    -import org.commonmark.node.Paragraph;
     import org.commonmark.parser.InlineParser;
     import org.commonmark.parser.SourceLine;
     import org.commonmark.parser.SourceLines;
    @@ -28,12 +26,10 @@ public class AlertBlockParser extends AbstractBlockParser {
         private static final Pattern ALERT_PATTERN_CUSTOM_TITLE = Pattern.compile("^\\[!([a-zA-Z]+)](.*)$");
     
         private final Alert block;
    -    private final String typeOriginalCase;
         private final String titleContent;
     
    -    private AlertBlockParser(String type, String typeOriginalCase, String titleContent) {
    +    private AlertBlockParser(String type, String titleContent) {
             this.block = new Alert(type);
    -        this.typeOriginalCase = typeOriginalCase;
             this.titleContent = titleContent;
         }
     
    @@ -76,16 +72,6 @@ public BlockContinue tryContinue(ParserState state) {
     
         @Override
         public void parseInlines(InlineParser inlineParser) {
    -        // Determine if there is any non-title body content.
    -        if (block.getFirstChild() == null) {
    -            /*
    -             * Replace the Alert with a BlockQuote whose only paragraph contains
    -             * the original first line text.
    -             */
    -            demoteToBlockQuote(inlineParser);
    -            return;
    -        }
    -
             if (titleContent.isEmpty()) {
                 return;
             }
    @@ -104,28 +90,6 @@ public void parseInlines(InlineParser inlineParser) {
             block.prependChild(titleNode);
         }
     
    -    private void demoteToBlockQuote(InlineParser inlineParser) {
    -        var bq = new BlockQuote();
    -        bq.setSourceSpans(block.getSourceSpans());
    -        var p = new Paragraph();
    -
    -        // Build the literal text including the alert marker and title.
    -        var literal = "[!" + typeOriginalCase + "]";
    -        if (!titleContent.isEmpty()) {
    -            /*
    -             * This may not preserve the original number of spaces between the
    -             * alert marker and title (e.g., if there were 0 or 2+ spaces).
    -             */
    -            literal += " " + titleContent;
    -        }
    -
    -        // Parse the inlines of the full content (alert marker + title)
    -        inlineParser.parse(SourceLines.of(SourceLine.of(literal, null)), p);
    -        bq.appendChild(p);
    -        block.insertAfter(bq);
    -        block.unlink();
    -    }
    -
         public static class Factory extends AbstractBlockParserFactory {
     
             private final Set allowedTypes;
    @@ -212,8 +176,7 @@ private BlockStart tryStartFresh(CharSequence line, int nextNonSpace, ParserStat
                     return BlockStart.none();
                 }
     
    -            var typeOriginalCase = matcher.group(1);
    -            var type = typeOriginalCase.toUpperCase(Locale.ROOT);
    +            var type = matcher.group(1).toUpperCase(Locale.ROOT);
                 if (!allowedTypes.contains(type)) {
                     return BlockStart.none();
                 }
    @@ -224,7 +187,7 @@ private BlockStart tryStartFresh(CharSequence line, int nextNonSpace, ParserStat
                 }
     
                 // Consume the rest of the first line.
    -            var start = BlockStart.of(new AlertBlockParser(type, typeOriginalCase, titleContent)).atIndex(line.length());
    +            var start = BlockStart.of(new AlertBlockParser(type, titleContent)).atIndex(line.length());
     
                 // If we got here via the promotion path, replace the empty BlockQuote.
                 var matched = state.getActiveBlockParser().getBlock();
    diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java
    index 8259b3239..db6f63524 100644
    --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java
    +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java
    @@ -214,19 +214,19 @@ public void customTitleIgnoresBlockContent() {
     
         @Test
         public void customTitleNoBody() {
    -        // Inlines should be parsed as usual after falling back to a block quote.
    +        // Alerts with no body are allowed.
             assertRenderingCustomTitles("> [!NOTE] Custom _title_\n>  \n>\n>",
    -                "
    \n" + - "

    [!NOTE] Custom title

    \n" + - "
    \n"); + "
    \n" + + "

    Custom title

    \n" + + "
    \n"); } @Test public void customTitleNoBodyNoSpace() { - assertRenderingCustomTitles("> [!NOTE] Custom _title_\n> \n>\n>", - "
    \n" + - "

    [!NOTE] Custom title

    \n" + - "
    \n"); + assertRenderingCustomTitles("> [!NOTE] Custom _title_", + "
    \n" + + "

    Custom title

    \n" + + "
    \n"); } @Test @@ -252,18 +252,18 @@ public void onlyHtmlCommentsInTitleUseDefaultTitle() { @Test public void noLazyContinuationAfterMarker() { assertRendering("> [!NOTE]\nBody text", - "
    \n" + - "

    [!NOTE]

    \n" + - "
    \n" + + "
    \n" + + "

    Note

    \n" + + "
    \n" + "

    Body text

    \n"); } @Test public void noLazyContinuationAfterTitle() { assertRenderingCustomTitles("> [!NOTE] Custom title\nBody text", - "
    \n" + - "

    [!NOTE] Custom title

    \n" + - "
    \n" + + "
    \n" + + "

    Custom title

    \n" + + "
    \n" + "

    Body text

    \n"); } diff --git a/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec-template.md b/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec-template.md index 9c1cf117b..e5f885038 100644 --- a/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec-template.md +++ b/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec-template.md @@ -134,20 +134,6 @@ Unconfigured custom type is not an alert: > Should be blockquote ```````````````````````````````` -Marker with no content: - -```````````````````````````````` example alert -> [!NOTE] -```````````````````````````````` - -Whitespace-only content after marker: - -```````````````````````````````` example alert -> [!TIP] -> -> -```````````````````````````````` - Extra space inside marker: ```````````````````````````````` example alert @@ -198,14 +184,6 @@ Leading spaces before blockquote marker: > Content ```````````````````````````````` -Blank line after marker ends the blockquote (not an alert): - -```````````````````````````````` example alert -> [!NOTE] - -Some text -```````````````````````````````` - Alert followed by blockquote: ```````````````````````````````` example alert diff --git a/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt b/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt index 6f041fee4..de4b9654a 100644 --- a/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt +++ b/commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt @@ -236,28 +236,6 @@ Should be blockquote

    ```````````````````````````````` -Marker with no content: - -```````````````````````````````` example alert -> [!NOTE] -. -
    -

    [!NOTE]

    -
    -```````````````````````````````` - -Whitespace-only content after marker: - -```````````````````````````````` example alert -> [!TIP] -> -> -. -
    -

    [!TIP]

    -
    -```````````````````````````````` - Extra space inside marker: ```````````````````````````````` example alert @@ -342,19 +320,6 @@ Leading spaces before blockquote marker: ```````````````````````````````` -Blank line after marker ends the blockquote (not an alert): - -```````````````````````````````` example alert -> [!NOTE] - -Some text -. -
    -

    [!NOTE]

    -
    -

    Some text

    -```````````````````````````````` - Alert followed by blockquote: ```````````````````````````````` example alert From def81c30cb434db7c96f0b1aaf244ed37652269b Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Mon, 1 Jun 2026 03:48:39 -0700 Subject: [PATCH 295/345] Track Source Positions --- .../gfm/alerts/internal/AlertBlockParser.java | 40 +++- .../commonmark/ext/gfm/alerts/AlertsTest.java | 192 ++++++++++++++---- 2 files changed, 182 insertions(+), 50 deletions(-) diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java index fc1cee97b..e7a9f402e 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java @@ -1,5 +1,6 @@ package org.commonmark.ext.gfm.alerts.internal; +import java.util.List; import java.util.Locale; import java.util.Set; import java.util.regex.Pattern; @@ -26,11 +27,11 @@ public class AlertBlockParser extends AbstractBlockParser { private static final Pattern ALERT_PATTERN_CUSTOM_TITLE = Pattern.compile("^\\[!([a-zA-Z]+)](.*)$"); private final Alert block; - private final String titleContent; + private final SourceLine titleLine; - private AlertBlockParser(String type, String titleContent) { + private AlertBlockParser(String type, SourceLine titleLine) { this.block = new Alert(type); - this.titleContent = titleContent; + this.titleLine = titleLine; } @Override @@ -72,7 +73,7 @@ public BlockContinue tryContinue(ParserState state) { @Override public void parseInlines(InlineParser inlineParser) { - if (titleContent.isEmpty()) { + if (titleLine == null || titleLine.getContent().length() == 0) { return; } @@ -84,7 +85,13 @@ public void parseInlines(InlineParser inlineParser) { * > But 3*3 = 9 */ var titleNode = new AlertTitle(); - inlineParser.parse(SourceLines.of(SourceLine.of(titleContent, null)), titleNode); + inlineParser.parse(SourceLines.of(titleLine), titleNode); + + // Set source spans on the title node from the source line + var sourceSpan = titleLine.getSourceSpan(); + if (sourceSpan != null) { + titleNode.setSourceSpans(List.of(sourceSpan)); + } // Body blocks were attached as children during block parsing. Prepend the title. block.prependChild(titleNode); @@ -181,13 +188,30 @@ private BlockStart tryStartFresh(CharSequence line, int nextNonSpace, ParserStat return BlockStart.none(); } - var titleContent = ""; + SourceLine titleLine = null; if (customTitlesAllowed) { - titleContent = matcher.group(2).replaceFirst("^[ \\t]+", "").stripTrailing(); + var fullSourceLine = state.getLine(); + var fullContent = fullSourceLine.getContent(); + + var groupStart = matcher.start(2); + var groupEnd = matcher.end(2); + var absStart = afterGt + groupStart; + var absEnd = afterGt + groupEnd; + + // Trim leading spaces/tabs + while (absStart < absEnd && Characters.isSpaceOrTab(fullContent, absStart)) { + absStart++; + } + // Trim trailing spaces/tabs + while (absEnd > absStart && Characters.isSpaceOrTab(fullContent, absEnd - 1)) { + absEnd--; + } + + titleLine = fullSourceLine.substring(absStart, absEnd); } // Consume the rest of the first line. - var start = BlockStart.of(new AlertBlockParser(type, titleContent)).atIndex(line.length()); + var start = BlockStart.of(new AlertBlockParser(type, titleLine)).atIndex(line.length()); // If we got here via the promotion path, replace the empty BlockQuote. var matched = state.getActiveBlockParser().getBlock(); diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java index db6f63524..fe01145a9 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -1,12 +1,17 @@ package org.commonmark.ext.gfm.alerts; import org.commonmark.Extension; -import org.commonmark.node.Node; +import org.commonmark.node.Emphasis; +import org.commonmark.node.SourceSpan; +import org.commonmark.node.StrongEmphasis; +import org.commonmark.node.Text; +import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; +import java.util.List; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -15,12 +20,13 @@ public class AlertsTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(AlertsExtension.create()); - private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES).build(); private static final HtmlRenderer HTML_RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); private static final Set EXTENSIONS_CUSTOM_TITLES = Set.of(AlertsExtension.builder().allowCustomTitles(true).build()); private static final Parser PARSER_CUSTOM_TITLES = Parser.builder() .extensions(EXTENSIONS_CUSTOM_TITLES) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) .build(); private static final HtmlRenderer HTML_RENDERER_CUSTOM_TITLES = HtmlRenderer.builder() .extensions(EXTENSIONS_CUSTOM_TITLES) @@ -39,12 +45,12 @@ private void assertRenderingCustomTitles(String source, String expectedResult) { @Test public void customType() { - Extension extension = AlertsExtension.builder() + var extension = AlertsExtension.builder() .addCustomType("INFO", "Information") .build(); - Parser parser = Parser.builder().extensions(Set.of(extension)).build(); - HtmlRenderer renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); + var parser = Parser.builder().extensions(Set.of(extension)).build(); + var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); assertThat(renderer.render(parser.parse("> [!INFO]\n> Custom alert"))).isEqualTo( "
    \n" + @@ -55,14 +61,14 @@ public void customType() { @Test public void multipleCustomTypes() { - Extension extension = AlertsExtension.builder() + var extension = AlertsExtension.builder() .addCustomType("INFO", "Information") .addCustomType("SUCCESS", "Success!") .addCustomType("DANGER", "Danger!") .build(); - Parser parser = Parser.builder().extensions(Set.of(extension)).build(); - HtmlRenderer renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); + var parser = Parser.builder().extensions(Set.of(extension)).build(); + var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); assertThat(renderer.render(parser.parse("> [!INFO]\n> Info content\n\n> [!SUCCESS]\n> Success content\n\n> [!DANGER]\n> Danger content"))).isEqualTo( "
    \n" + @@ -81,12 +87,12 @@ public void multipleCustomTypes() { @Test public void standardTypesWithCustomConfigured() { - Extension extension = AlertsExtension.builder() + var extension = AlertsExtension.builder() .addCustomType("INFO", "Information") .build(); - Parser parser = Parser.builder().extensions(Set.of(extension)).build(); - HtmlRenderer renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); + var parser = Parser.builder().extensions(Set.of(extension)).build(); + var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); assertThat(renderer.render(parser.parse("> [!NOTE]\n> Standard type"))).isEqualTo( "
    \n" + @@ -97,12 +103,12 @@ public void standardTypesWithCustomConfigured() { @Test public void overrideStandardTypeTitle() { - Extension extension = AlertsExtension.builder() + var extension = AlertsExtension.builder() .addCustomType("NOTE", "Nota") .build(); - Parser parser = Parser.builder().extensions(Set.of(extension)).build(); - HtmlRenderer renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); + var parser = Parser.builder().extensions(Set.of(extension)).build(); + var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); assertThat(renderer.render(parser.parse("> [!NOTE]\n> Localized title"))).isEqualTo( "
    \n" + @@ -111,6 +117,26 @@ public void overrideStandardTypeTitle() { "
    \n"); } + // Custom type validation + + @Test + public void customTypeMustBeUppercase() { + assertThrows(IllegalArgumentException.class, () -> + AlertsExtension.builder().addCustomType("info", "Information").build()); + } + + @Test + public void customTypeMustNotBeEmpty() { + assertThrows(IllegalArgumentException.class, () -> + AlertsExtension.builder().addCustomType("", "Title").build()); + } + + @Test + public void customTypeTitleMustNotBeEmpty() { + assertThrows(IllegalArgumentException.class, () -> + AlertsExtension.builder().addCustomType("INFO", "").build()); + } + // Custom titles @Test @@ -335,9 +361,9 @@ public void noNestedAlertsByDefaultLeadingEmptyLines() { @Test public void nestedAlerts() { - Extension extension = AlertsExtension.builder().allowNestedAlerts(true).build(); - Parser parser = Parser.builder().extensions(Set.of(extension)).build(); - HtmlRenderer renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); + var extension = AlertsExtension.builder().allowNestedAlerts(true).build(); + var parser = Parser.builder().extensions(Set.of(extension)).build(); + var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); var source = String.join("\n", "> [!TIP]", @@ -383,49 +409,131 @@ public void nestedAlerts() { assertThat(renderer.render(parser.parse(source))).isEqualTo(expected); } - // Custom type validation + // AST @Test - public void customTypeMustBeUppercase() { - assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().addCustomType("info", "Information").build()); + public void alertParsedAsAlertNode() { + var document = PARSER.parse("> [!NOTE]\n> This is a note"); + var firstChild = document.getFirstChild(); + assertThat(firstChild).isInstanceOf(Alert.class); + var alert = (Alert) firstChild; + assertThat(alert.getType()).isEqualTo("NOTE"); } @Test - public void customTypeMustNotBeEmpty() { - assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().addCustomType("", "Title").build()); + public void customTypeParsedAsAlertNode() { + var extension = AlertsExtension.builder() + .addCustomType("INFO", "Information") + .build(); + + var parser = Parser.builder().extensions(Set.of(extension)).build(); + + var document = parser.parse("> [!INFO]\n> Custom alert"); + var alert = (Alert) document.getFirstChild(); + + assertThat(alert.getType()).isEqualTo("INFO"); } + // Source positions + @Test - public void customTypeTitleMustNotBeEmpty() { - assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().addCustomType("INFO", "").build()); + public void titleSourcePositionPreserved() { + var source = "> [!NOTE] Custom title\n> Body text"; + var document = PARSER_CUSTOM_TITLES.parse(source); + var alert = (Alert) document.getFirstChild(); + var title = (AlertTitle) alert.getFirstChild(); + + // "Custom title" is at column 10, length 12 in line 0 + assertThat(title.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 10, 10, 12))); } - // AST + @Test + public void titleSourcePositionPreservedBetweenBlocks() { + var source = "- List\n\n> [!NOTE] Custom title\n> Body text\n\nPlain paragraph"; + var document = PARSER_CUSTOM_TITLES.parse(source); + var alert = (Alert) document.getFirstChild().getNext(); + var title = (AlertTitle) alert.getFirstChild(); + + // "Custom title" is at column 10, length 12 in line 2 + assertThat(title.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 10, 18, 12))); + } @Test - public void alertParsedAsAlertNode() { - Node document = PARSER.parse("> [!NOTE]\n> This is a note"); - Node firstChild = document.getFirstChild(); - assertThat(firstChild).isInstanceOf(Alert.class); - Alert alert = (Alert) firstChild; - assertThat(alert.getType()).isEqualTo("NOTE"); + public void titleSourcePositionWithLeadingAndTrailingSpaces() { + var source = "> [!NOTE] Custom title \n> Body text"; + var document = PARSER_CUSTOM_TITLES.parse(source); + var alert = (Alert) document.getFirstChild(); + var title = (AlertTitle) alert.getFirstChild(); + + // Both leading and trailing spaces are trimmed + assertThat(title.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 13, 13, 12))); } @Test - public void customTypeParsedAsAlertNode() { - Extension extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") - .build(); + public void titleWithInlineFormattingSourcePosition() { + var source = "> [!NOTE] Custom _title_\n> Body text"; + var document = PARSER_CUSTOM_TITLES.parse(source); + var alert = (Alert) document.getFirstChild(); + var title = (AlertTitle) alert.getFirstChild(); - Parser parser = Parser.builder().extensions(Set.of(extension)).build(); + // "Custom _title_" is at column 10, length 14 + assertThat(title.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 10, 10, 14))); - Node document = parser.parse("> [!INFO]\n> Custom alert"); - Alert alert = (Alert) document.getFirstChild(); + // First child: "Custom " text node + var firstText = title.getFirstChild(); + assertThat(firstText).isInstanceOf(Text.class); + assertThat(((Text) firstText).getLiteral()).isEqualTo("Custom "); + assertThat(firstText.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 10, 10, 7))); - assertThat(alert.getType()).isEqualTo("INFO"); + // Second child: emphasis node containing "title" + var emphasis = firstText.getNext(); + assertThat(emphasis).isInstanceOf(Emphasis.class); + assertThat(emphasis.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 17, 17, 7))); + + // Text inside emphasis: "title" + var titleText = emphasis.getFirstChild(); + assertThat(titleText).isInstanceOf(Text.class); + assertThat(((Text) titleText).getLiteral()).isEqualTo("title"); + assertThat(titleText.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 18, 18, 5))); + } + + @Test + public void titleWithNestedInlineFormattingSourcePosition() { + var source = "> [!NOTE] Text with **bold _and italic_**\n> Body text"; + var document = PARSER_CUSTOM_TITLES.parse(source); + var alert = (Alert) document.getFirstChild(); + var title = (AlertTitle) alert.getFirstChild(); + + // "Custom _title_" is at column 10, length 14 + assertThat(title.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 10, 10, 31))); + + // First child: "Text with " text node + var firstText = title.getFirstChild(); + assertThat(firstText).isInstanceOf(Text.class); + assertThat(((Text) firstText).getLiteral()).isEqualTo("Text with "); + assertThat(firstText.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 10, 10, 10))); + + // Second child: strong emphasis node + var strong = firstText.getNext(); + assertThat(strong).isInstanceOf(StrongEmphasis.class); + assertThat(strong.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 20, 20, 21))); + + // Inside strong: "bold " text + var boldText = strong.getFirstChild(); + assertThat(boldText).isInstanceOf(Text.class); + assertThat(((Text) boldText).getLiteral()).isEqualTo("bold "); + assertThat(boldText.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 22, 22, 5))); + + // Inside strong: emphasis node with "and italic" + var emphasis = boldText.getNext(); + assertThat(emphasis).isInstanceOf(Emphasis.class); + assertThat(emphasis.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 27, 27, 12))); + + // Text inside emphasis: "and italic" + var italicText = emphasis.getFirstChild(); + assertThat(italicText).isInstanceOf(Text.class); + assertThat(((Text) italicText).getLiteral()).isEqualTo("and italic"); + assertThat(italicText.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 28, 28, 10))); } } From 348d18b8ab667157582ff0ca2f60880219b3757e Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Mon, 1 Jun 2026 03:48:46 -0700 Subject: [PATCH 296/345] Remove HTML Comment Handling --- .../internal/AlertHtmlNodeRenderer.java | 49 ------------------- .../commonmark/ext/gfm/alerts/AlertsTest.java | 18 ------- 2 files changed, 67 deletions(-) diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java index 062f637e1..af9fff704 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -2,9 +2,7 @@ import org.commonmark.ext.gfm.alerts.Alert; import org.commonmark.ext.gfm.alerts.AlertTitle; -import org.commonmark.node.HtmlInline; import org.commonmark.node.Node; -import org.commonmark.node.Text; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; @@ -40,21 +38,6 @@ protected void renderAlert(Alert alert) { htmlWriter.tag("p", context.extendAttributes(alert, "p", Map.of("class", "markdown-alert-title"))); var first = alert.getFirstChild(); if (first instanceof AlertTitle) { - /* - * If the alert title only contains HTML comments like this: - * - * > [!TIP] - * > Body text - * - * Then the reader will see a blank title. In this case, it's better - * to render the default title (but keep the comments for accuracy). - * - * These comments will be visible when rendered to other formats like - * Markdown, so this is only relevant for the HTML renderer. - */ - if (isCommentOnlyTitle(first)) { - htmlWriter.text(getAlertTitle(type)); - } renderChildren(first); } else { htmlWriter.text(getAlertTitle(type)); @@ -90,38 +73,6 @@ private String getAlertTitle(String type) { } } - private boolean isCommentOnlyTitle(Node title) { - var node = title.getFirstChild(); - if (node == null) { - return false; - } - while (node != null) { - if (node instanceof HtmlInline) { - if (!isHtmlComment((HtmlInline) node)) { - return false; - } - } else if (node instanceof Text) { - if (!((Text) node).getLiteral().trim().isEmpty()) { - return false; - } - } else { - return false; - } - node = node.getNext(); - } - return true; - } - - private boolean isHtmlComment(HtmlInline htmlInline) { - var literal = htmlInline.getLiteral(); - if (literal == null || !literal.startsWith("") - || literal.equals("") - || literal.endsWith("-->"); - } - private void renderChildren(Node parent) { var node = parent.getFirstChild(); while (node != null) { diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java index fe01145a9..e39ce8f75 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -184,15 +184,6 @@ public void customTitleWithComment() { "
    \n"); } - @Test - public void customTitleWithOnlyCommentFallsBackToDefaultTitle() { - assertRenderingCustomTitles("> [!TIP] \n> Body text", - "
    \n" + - "

    Tip

    \n" + - "

    Body text

    \n" + - "
    \n"); - } - @Test public void customTitleWithInlineFormatting() { assertRenderingCustomTitles("> [!NOTE] Custom _title with **formatting**_\n> Note with a custom title", @@ -264,15 +255,6 @@ public void onlyTrailingWhitespaceIsNotCustomTitle() { "
    \n"); } - @Test - public void onlyHtmlCommentsInTitleUseDefaultTitle() { - assertRenderingCustomTitles("> [!NOTE] \n> Body text", - "
    \n" + - "

    Note

    \n" + - "

    Body text

    \n" + - "
    \n"); - } - // Lazy continuation @Test From d09c3c053d7bd17d6892f929bdc5c78fe5de3a41 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:13:33 -0700 Subject: [PATCH 297/345] Mention Inline Parsing of Titles in AlertsExtension --- .../commonmark/ext/gfm/alerts/AlertsExtension.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index cb29fd5aa..e540484fc 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -21,9 +21,10 @@ * Extension for GFM alerts using {@code [!TYPE]} syntax (GitHub Flavored Markdown). *

    * Create with {@link #create()} or {@link #builder()} and configure on builders - * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, - * {@link HtmlRenderer.Builder#extensions(Iterable)}). - * Parsed alerts become {@link Alert} blocks. + * ({@link Parser.Builder#extensions(Iterable)}, {@link HtmlRenderer.Builder#extensions(Iterable)}). + * Parsed alerts become {@link Alert} blocks. If custom alert titles are allowed + * via {@link Builder#allowCustomTitles(boolean)}, the inline formatting of those + * titles will be parsed into {@link AlertTitle} nodes. * * The {@link #create() default configuration} of this extension will match GFM * exactly, with the following exceptions: @@ -129,9 +130,11 @@ public Builder addCustomType(String type, String title) { } /** - * Allows or disallows custom titles on alerts. See {@link AlertTitle} for more information. + * Allows or disallows custom titles on alerts. Inline formatting is supported + * within these titles. * @param allow Whether to allow or disallow custom titles on alerts. * @return {@code this} + * @see AlertTitle */ public Builder allowCustomTitles(boolean allow) { customTitlesAllowed = allow; From 4256dc7efe6979a94115cefd53e84ce3d679ab74 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Wed, 3 Jun 2026 22:52:20 -0700 Subject: [PATCH 298/345] Allow Removing Standard GFM Alert Types --- CHANGELOG.md | 5 ++ commonmark-ext-gfm-alerts/README.md | 7 ++ .../ext/gfm/alerts/AlertsExtension.java | 64 +++++++++++++++---- .../internal/AlertHtmlNodeRenderer.java | 27 ++------ .../commonmark/ext/gfm/alerts/AlertsTest.java | 25 ++++++++ 5 files changed, 96 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 254826f18..ae6597b4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,11 @@ with the exception that 0.x versions can break between minor versions. other blocks (including other alerts). See [this section of the alerts README](./commonmark-ext-gfm-alerts/README.md#nesting-alerts) for more information. + - New configuration for `AlertsExtension` to allow alert types (including standard + GFM types) to be removed (disallowed). + ```java + var extension = AlertsExtension.builder().removeTypes("NOTE", "TIP").build(); + ``` ## [0.28.0] - 2026-03-31 ### Added diff --git a/commonmark-ext-gfm-alerts/README.md b/commonmark-ext-gfm-alerts/README.md index 026408a56..9eb8f408e 100644 --- a/commonmark-ext-gfm-alerts/README.md +++ b/commonmark-ext-gfm-alerts/README.md @@ -36,6 +36,13 @@ var extension = AlertsExtension.builder() Custom types must be UPPERCASE. Standard type titles can also be overridden for localization. +If any types (including the five standard GFM types) aren't desired, they can be +removed (disallowed): + +```java +var extension = AlertsExtension.builder().removeTypes("NOTE", "TIP").build(); +``` + ### Custom Alert Titles Allow authors to provide custom titles per alert by adding text after the alert diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index e540484fc..bff555cfb 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -49,14 +49,28 @@ public class AlertsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, MarkdownRenderer.MarkdownRendererExtension { - static final Set STANDARD_TYPES = Set.of("NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION"); + /** + * The standard GitHub Flavored Markdown (GFM) types that the extension + * enables by default. They can be removed individually with + * {@link Builder#removeTypes(String...)}. + */ + public static final Map STANDARD_TYPES = Map.ofEntries( + Map.entry("NOTE", "Note"), + Map.entry("TIP", "Tip"), + Map.entry("IMPORTANT", "Important"), + Map.entry("WARNING", "Warning"), + Map.entry("CAUTION", "Caution") + ); - private final Map customTypes; + /** + * A map of alert marker ({@code [!TYPE]}) to the default title for that marker. + */ + private final Map allowedTypes; private final boolean customTitlesAllowed; private final boolean nestedAlertsAllowed; private AlertsExtension(Builder builder) { - this.customTypes = new HashMap<>(builder.customTypes); + this.allowedTypes = new HashMap<>(builder.allowedTypes); this.customTitlesAllowed = builder.customTitlesAllowed; this.nestedAlertsAllowed = builder.nestedAlertsAllowed; } @@ -71,15 +85,14 @@ public static Builder builder() { @Override public void extend(Parser.Builder parserBuilder) { - var allowedTypes = new HashSet<>(STANDARD_TYPES); - allowedTypes.addAll(customTypes.keySet()); + var allowedTypesSet = new HashSet<>(allowedTypes.keySet()); parserBuilder.customBlockParserFactory( - new AlertBlockParser.Factory(allowedTypes, customTitlesAllowed, nestedAlertsAllowed)); + new AlertBlockParser.Factory(allowedTypesSet, customTitlesAllowed, nestedAlertsAllowed)); } @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(context -> new AlertHtmlNodeRenderer(context, customTypes)); + rendererBuilder.nodeRendererFactory(context -> new AlertHtmlNodeRenderer(context, allowedTypes)); } @Override @@ -101,18 +114,18 @@ public Set getSpecialCharacters() { * Builder for configuring the alerts extension. */ public static class Builder { - private final Map customTypes = new HashMap<>(); + private final Map allowedTypes = new HashMap<>(STANDARD_TYPES); private boolean customTitlesAllowed = false; private boolean nestedAlertsAllowed = false; /** - * Adds a custom alert type with a display title. + * Adds a custom alert type with a default title. *

    - * This can also be used to override the display title of standard GFM types + * This can also be used to override the default title of standard GFM types * (e.g., for localization). * * @param type the alert type (must be uppercase) - * @param title the display title for this alert type + * @param title the default title for this alert type * @return {@code this} */ public Builder addCustomType(String type, String title) { @@ -125,7 +138,34 @@ public Builder addCustomType(String type, String title) { if (!type.equals(type.toUpperCase(Locale.ROOT))) { throw new IllegalArgumentException("Type must be uppercase: " + type); } - customTypes.put(type, title); + allowedTypes.put(type, title); + return this; + } + + /** + * Removes alert types from the allowed list. + * + * @param types the alert types to remove (must be uppercase) + * @return {@code this} + * @see AlertsExtension#STANDARD_TYPES + */ + public Builder removeTypes(String... types) { + if (types == null) { + throw new IllegalArgumentException("Types must not be null"); + } + + for (String type : types) { + if (type == null || type.isEmpty()) { + throw new IllegalArgumentException("Each type must not be null or empty"); + } + + if (!type.equals(type.toUpperCase(Locale.ROOT))) { + throw new IllegalArgumentException("Type must be uppercase: " + type); + } + + allowedTypes.remove(type); + } + return this; } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java index af9fff704..be337d5be 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -13,12 +13,12 @@ public class AlertHtmlNodeRenderer extends AlertNodeRenderer { private final HtmlWriter htmlWriter; private final HtmlNodeRendererContext context; - private final Map customTypeTitles; + private final Map allowedTypes; - public AlertHtmlNodeRenderer(HtmlNodeRendererContext context, Map customTypeTitles) { + public AlertHtmlNodeRenderer(HtmlNodeRendererContext context, Map allowedTypes) { this.htmlWriter = context.getWriter(); this.context = context; - this.customTypeTitles = customTypeTitles; + this.allowedTypes = allowedTypes; } @Override @@ -53,24 +53,11 @@ protected void renderAlert(Alert alert) { } private String getAlertTitle(String type) { - var customTypeTitle = customTypeTitles.get(type); - if (customTypeTitle != null) { - return customTypeTitle; - } - switch (type) { - case "NOTE": - return "Note"; - case "TIP": - return "Tip"; - case "IMPORTANT": - return "Important"; - case "WARNING": - return "Warning"; - case "CAUTION": - return "Caution"; - default: - throw new IllegalStateException("Unknown alert type: " + type); + var typeTitle = allowedTypes.get(type); + if (typeTitle == null) { + throw new IllegalStateException("Unknown alert type: " + type); } + return typeTitle; } private void renderChildren(Node parent) { diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java index e39ce8f75..bd5a59a0b 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -137,6 +137,31 @@ public void customTypeTitleMustNotBeEmpty() { AlertsExtension.builder().addCustomType("INFO", "").build()); } + @Test + public void removeStandardTypes() { + var extension = AlertsExtension.builder().removeTypes("NOTE", "TIP").build(); + var parser = Parser.builder().extensions(Set.of(extension)).build(); + var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); + + assertThat(renderer.render(parser.parse("> [!NOTE]\n> Regular block quote"))).isEqualTo( + "

    \n" + + "

    [!NOTE]\n" + + "Regular block quote

    \n" + + "
    \n"); + + assertThat(renderer.render(parser.parse("> [!TIP]\n> Regular block quote"))).isEqualTo( + "
    \n" + + "

    [!TIP]\n" + + "Regular block quote

    \n" + + "
    \n"); + + assertThat(renderer.render(parser.parse("> [!IMPORTANT]\n> Alert"))).isEqualTo( + "
    \n" + + "

    Important

    \n" + + "

    Alert

    \n" + + "
    \n"); + } + // Custom titles @Test From d6657350ffaa8122894e1e9ae92bb352d08543ce Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Sat, 6 Jun 2026 13:54:15 -0700 Subject: [PATCH 299/345] Change removeTypes to setAllowedTypes --- CHANGELOG.md | 12 +++- commonmark-ext-gfm-alerts/README.md | 11 ++- .../ext/gfm/alerts/AlertsExtension.java | 71 +++++++++++-------- .../commonmark/ext/gfm/alerts/AlertsTest.java | 4 +- 4 files changed, 61 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae6597b4f..8f725d366 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,10 +20,16 @@ with the exception that 0.x versions can break between minor versions. other blocks (including other alerts). See [this section of the alerts README](./commonmark-ext-gfm-alerts/README.md#nesting-alerts) for more information. - - New configuration for `AlertsExtension` to allow alert types (including standard - GFM types) to be removed (disallowed). + - New configuration for `AlertsExtension` to allow the set of alert types + (including standard GFM types) to be completely overwritten. ```java - var extension = AlertsExtension.builder().removeTypes("NOTE", "TIP").build(); + var extension = AlertsExtension.builder() + .setAllowedTypes(Map.ofEntries( + Map.entry("IMPORTANT", "Important"), + Map.entry("WARNING", "Warning") + Map.entry("BUG", "Known Bug") + )) + .build(); ``` ## [0.28.0] - 2026-03-31 diff --git a/commonmark-ext-gfm-alerts/README.md b/commonmark-ext-gfm-alerts/README.md index 9eb8f408e..fddff7f0d 100644 --- a/commonmark-ext-gfm-alerts/README.md +++ b/commonmark-ext-gfm-alerts/README.md @@ -36,11 +36,16 @@ var extension = AlertsExtension.builder() Custom types must be UPPERCASE. Standard type titles can also be overridden for localization. -If any types (including the five standard GFM types) aren't desired, they can be -removed (disallowed): +The allowed types (including the five standard GFM types) can also be completely overwritten: ```java -var extension = AlertsExtension.builder().removeTypes("NOTE", "TIP").build(); +var extension = AlertsExtension.builder() + .setAllowedTypes(Map.ofEntries( + Map.entry("IMPORTANT", "Important"), + Map.entry("WARNING", "Warning") + Map.entry("BUG", "Known Bug") + )) + .build(); ``` ### Custom Alert Titles diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index bff555cfb..e95a51eb1 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -15,6 +15,7 @@ import java.util.Locale; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -51,8 +52,7 @@ public class AlertsExtension implements Parser.ParserExtension, HtmlRenderer.Htm /** * The standard GitHub Flavored Markdown (GFM) types that the extension - * enables by default. They can be removed individually with - * {@link Builder#removeTypes(String...)}. + * enables by default. These can be overwritten with {@link Builder#setAllowedTypes(Map)}. */ public static final Map STANDARD_TYPES = Map.ofEntries( Map.entry("NOTE", "Note"), @@ -114,10 +114,46 @@ public Set getSpecialCharacters() { * Builder for configuring the alerts extension. */ public static class Builder { - private final Map allowedTypes = new HashMap<>(STANDARD_TYPES); + private Map allowedTypes = new HashMap<>(STANDARD_TYPES); private boolean customTitlesAllowed = false; private boolean nestedAlertsAllowed = false; + /** + * Sets which alert types will be recognized and parsed into {@link Alert} blocks, + * completely overwriting any previous configuration. + *

    + * By default, {@link AlertsExtension#STANDARD_TYPES} are used. + * + * @param allowedTypes A map of alert type to the default title for that type. + * Must not be null/empty or contain any null/empty keys or + * values. Additionally, all alert types must be uppercase. + * @return {@code this} + */ + public Builder setAllowedTypes(Map allowedTypes) { + Objects.requireNonNull(allowedTypes, "allowedTypes must not be null"); + if (allowedTypes.isEmpty()) { + throw new IllegalArgumentException("allowedTypes must not be empty"); + } + + for (Map.Entry entry : allowedTypes.entrySet()) { + var type = Objects.requireNonNull(entry.getKey(), "Types must not be null"); + if (type.isEmpty()) { + throw new IllegalArgumentException("Types must not be empty"); + } + if (!type.equals(type.toUpperCase(Locale.ROOT))) { + throw new IllegalArgumentException("Types must be uppercase: " + type); + } + + var defaultTitle = Objects.requireNonNull(entry.getValue(), "Default titles must not be null: " + type); + if (defaultTitle.isEmpty()) { + throw new IllegalArgumentException("Default titles must not be empty: " + type); + } + } + + this.allowedTypes = Map.copyOf(allowedTypes); + return this; + } + /** * Adds a custom alert type with a default title. *

    @@ -142,36 +178,10 @@ public Builder addCustomType(String type, String title) { return this; } - /** - * Removes alert types from the allowed list. - * - * @param types the alert types to remove (must be uppercase) - * @return {@code this} - * @see AlertsExtension#STANDARD_TYPES - */ - public Builder removeTypes(String... types) { - if (types == null) { - throw new IllegalArgumentException("Types must not be null"); - } - - for (String type : types) { - if (type == null || type.isEmpty()) { - throw new IllegalArgumentException("Each type must not be null or empty"); - } - - if (!type.equals(type.toUpperCase(Locale.ROOT))) { - throw new IllegalArgumentException("Type must be uppercase: " + type); - } - - allowedTypes.remove(type); - } - - return this; - } - /** * Allows or disallows custom titles on alerts. Inline formatting is supported * within these titles. + * * @param allow Whether to allow or disallow custom titles on alerts. * @return {@code this} * @see AlertTitle @@ -189,6 +199,7 @@ public Builder allowCustomTitles(boolean allow) { *

    * Note that even when this is allowed, {@link Parser.Builder#maxOpenBlockParsers(int)} * will be respected. + * * @param allow Whether to allow or disallow parsing alerts within non-root blocks. * @return {@code this} */ diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java index bd5a59a0b..a029a6a9d 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test; import java.util.List; +import java.util.Map; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -139,7 +140,8 @@ public void customTypeTitleMustNotBeEmpty() { @Test public void removeStandardTypes() { - var extension = AlertsExtension.builder().removeTypes("NOTE", "TIP").build(); + var allowedTypes = Map.ofEntries(Map.entry("IMPORTANT", "Important")); + var extension = AlertsExtension.builder().setAllowedTypes(allowedTypes).build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); From 9c813f6053400b06ce3dcb267c2b4111f3ea8e34 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Sat, 6 Jun 2026 14:11:50 -0700 Subject: [PATCH 300/345] Remove addCustomType --- CHANGELOG.md | 14 ++++++-- commonmark-ext-gfm-alerts/README.md | 19 +++-------- .../ext/gfm/alerts/AlertsExtension.java | 24 -------------- .../alerts/AlertsMarkdownRendererTest.java | 33 ++++++++++--------- .../commonmark/ext/gfm/alerts/AlertsTest.java | 31 +++++++++++------ .../gfm/alerts/examples/AlertsExample.java | 5 ++- 6 files changed, 60 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f725d366..c9fd67a41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,9 @@ with the exception that 0.x versions can break between minor versions. other blocks (including other alerts). See [this section of the alerts README](./commonmark-ext-gfm-alerts/README.md#nesting-alerts) for more information. - - New configuration for `AlertsExtension` to allow the set of alert types - (including standard GFM types) to be completely overwritten. + - **Breaking:** `AlertsExtension.Builder#addCustomType` has been changed to + `setAllowedTypes` to allow the set of alert types (including standard GFM types) + to be completely overwritten. ```java var extension = AlertsExtension.builder() .setAllowedTypes(Map.ofEntries( @@ -31,6 +32,15 @@ with the exception that 0.x versions can break between minor versions. )) .build(); ``` + Example migration: + ```diff + + var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); + + customTypes.put("BUG", "Known Bug"); + var extension = AlertsExtension.builder() + - .addCustomType("BUG", "Known Bug") + + .setAllowedTypes(customTypes) + .build(); + ``` ## [0.28.0] - 2026-03-31 ### Added diff --git a/commonmark-ext-gfm-alerts/README.md b/commonmark-ext-gfm-alerts/README.md index fddff7f0d..c61987c76 100644 --- a/commonmark-ext-gfm-alerts/README.md +++ b/commonmark-ext-gfm-alerts/README.md @@ -26,28 +26,19 @@ var renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); ### Custom Alert Types -Add custom types beyond the five standard GFM types: +By default, the five standard GFM alert types are used. These can be added to or replaced +with a new set of alert types: ```java +var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); +customTypes.put("BUG", "Known Bug"); var extension = AlertsExtension.builder() - .addCustomType("BUG", "Known Bug") + .setAllowedTypes(customTypes) .build(); ``` Custom types must be UPPERCASE. Standard type titles can also be overridden for localization. -The allowed types (including the five standard GFM types) can also be completely overwritten: - -```java -var extension = AlertsExtension.builder() - .setAllowedTypes(Map.ofEntries( - Map.entry("IMPORTANT", "Important"), - Map.entry("WARNING", "Warning") - Map.entry("BUG", "Known Bug") - )) - .build(); -``` - ### Custom Alert Titles Allow authors to provide custom titles per alert by adding text after the alert diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index e95a51eb1..48a09e02d 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -154,30 +154,6 @@ public Builder setAllowedTypes(Map allowedTypes) { return this; } - /** - * Adds a custom alert type with a default title. - *

    - * This can also be used to override the default title of standard GFM types - * (e.g., for localization). - * - * @param type the alert type (must be uppercase) - * @param title the default title for this alert type - * @return {@code this} - */ - public Builder addCustomType(String type, String title) { - if (type == null || type.isEmpty()) { - throw new IllegalArgumentException("Type must not be null or empty"); - } - if (title == null || title.isEmpty()) { - throw new IllegalArgumentException("Title must not be null or empty"); - } - if (!type.equals(type.toUpperCase(Locale.ROOT))) { - throw new IllegalArgumentException("Type must be uppercase: " + type); - } - allowedTypes.put(type, title); - return this; - } - /** * Allows or disallows custom titles on alerts. Inline formatting is supported * within these titles. diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java index f9deb8dd6..1409dc8ef 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java @@ -5,6 +5,7 @@ import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.jupiter.api.Test; +import java.util.HashMap; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -40,41 +41,43 @@ public void allStandardTypesRoundTrip() { @Test public void lowercaseTypeRendersAsUppercase() { // Lowercase input gets normalized to uppercase type - String rendered = RENDERER.render(PARSER.parse("> [!note]\n> Content\n")); + var rendered = RENDERER.render(PARSER.parse("> [!note]\n> Content\n")); assertThat(rendered).isEqualTo("> [!NOTE]\n> Content\n"); } @Test public void leadingAndTrailingLinesAreRemoved() { - String rendered = RENDERER.render(PARSER.parse(">\n> \n>[!NOTE]\n> Content\n>\n> \n")); + var rendered = RENDERER.render(PARSER.parse(">\n> \n>[!NOTE]\n> Content\n>\n> \n")); assertThat(rendered).isEqualTo("> [!NOTE]\n> Content\n"); } @Test public void alertWithMultipleParagraphs() { - String input = "> [!NOTE]\n> First paragraph\n>\n> Second paragraph\n"; + var input = "> [!NOTE]\n> First paragraph\n>\n> Second paragraph\n"; // MarkdownWriter always writes the prefix including trailing space - String expected = "> [!NOTE]\n> First paragraph\n> \n> Second paragraph\n"; - String rendered = RENDERER.render(PARSER.parse(input)); + var expected = "> [!NOTE]\n> First paragraph\n> \n> Second paragraph\n"; + var rendered = RENDERER.render(PARSER.parse(input)); assertThat(rendered).isEqualTo(expected); } @Test public void customTypeRoundTrip() { - Extension extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") + var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); + customTypes.put("INFO", "Information"); + var extension = AlertsExtension.builder() + .setAllowedTypes(customTypes) .build(); - Parser parser = Parser.builder().extensions(Set.of(extension)).build(); - MarkdownRenderer renderer = MarkdownRenderer.builder().extensions(Set.of(extension)).build(); - String input = "> [!INFO]\n> Custom type\n"; + var parser = Parser.builder().extensions(Set.of(extension)).build(); + var renderer = MarkdownRenderer.builder().extensions(Set.of(extension)).build(); + var input = "> [!INFO]\n> Custom type\n"; assertRoundTrip(input, parser, renderer); } @Test public void alertWithList() { - String input = "> [!NOTE]\n> Items:\n> \n> - First\n> - Second\n"; + var input = "> [!NOTE]\n> Items:\n> \n> - First\n> - Second\n"; assertRoundTrip(input); } @@ -92,17 +95,17 @@ public void customTitleWithFormattingRoundTrip() { @Test public void customTitleWithMultipleBlocks() { - String input = "> [!NOTE]Title\n> First paragraph\n>\n> Second paragraph\n>\n> - > Nested blocks\n"; + var input = "> [!NOTE]Title\n> First paragraph\n>\n> Second paragraph\n>\n> - > Nested blocks\n"; // MarkdownWriter always writes the prefix including trailing space - String expected = "> [!NOTE] Title\n> First paragraph\n> \n> Second paragraph\n> \n> - > Nested blocks\n"; - String rendered = RENDERER_CUSTOM_TITLES.render(PARSER_CUSTOM_TITLES.parse(input)); + var expected = "> [!NOTE] Title\n> First paragraph\n> \n> Second paragraph\n> \n> - > Nested blocks\n"; + var rendered = RENDERER_CUSTOM_TITLES.render(PARSER_CUSTOM_TITLES.parse(input)); assertThat(rendered).isEqualTo(expected); } // Helpers private void assertRoundTrip(String input, Parser parser, MarkdownRenderer renderer) { - String rendered = renderer.render(parser.parse(input)); + var rendered = renderer.render(parser.parse(input)); assertThat(rendered).isEqualTo(input); } diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java index a029a6a9d..0b1b08e67 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -11,6 +11,7 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -46,8 +47,10 @@ private void assertRenderingCustomTitles(String source, String expectedResult) { @Test public void customType() { + var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); + customTypes.put("INFO", "Information"); var extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") + .setAllowedTypes(customTypes) .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); @@ -63,9 +66,11 @@ public void customType() { @Test public void multipleCustomTypes() { var extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") - .addCustomType("SUCCESS", "Success!") - .addCustomType("DANGER", "Danger!") + .setAllowedTypes(Map.ofEntries( + Map.entry("INFO", "Information"), + Map.entry("SUCCESS", "Success!"), + Map.entry("DANGER", "Danger!") + )) .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); @@ -88,8 +93,10 @@ public void multipleCustomTypes() { @Test public void standardTypesWithCustomConfigured() { + var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); + customTypes.put("INFO", "Information"); var extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") + .setAllowedTypes(customTypes) .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); @@ -104,8 +111,10 @@ public void standardTypesWithCustomConfigured() { @Test public void overrideStandardTypeTitle() { + var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); + customTypes.put("NOTE", "Nota"); var extension = AlertsExtension.builder() - .addCustomType("NOTE", "Nota") + .setAllowedTypes(customTypes) .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); @@ -123,19 +132,19 @@ public void overrideStandardTypeTitle() { @Test public void customTypeMustBeUppercase() { assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().addCustomType("info", "Information").build()); + AlertsExtension.builder().setAllowedTypes(Map.of("info", "Information")).build()); } @Test public void customTypeMustNotBeEmpty() { assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().addCustomType("", "Title").build()); + AlertsExtension.builder().setAllowedTypes(Map.of("", "Title")).build()); } @Test public void customTypeTitleMustNotBeEmpty() { assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().addCustomType("INFO", "").build()); + AlertsExtension.builder().setAllowedTypes(Map.of("INFO", "")).build()); } @Test @@ -431,8 +440,10 @@ public void alertParsedAsAlertNode() { @Test public void customTypeParsedAsAlertNode() { + var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); + customTypes.put("INFO", "Information"); var extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") + .setAllowedTypes(customTypes) .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java index 34b78385c..28969ac5c 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java @@ -4,6 +4,7 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; +import java.util.HashMap; import java.util.List; /** @@ -55,8 +56,10 @@ private static void customTypesExample() { System.out.println("CUSTOM ALERT TYPES"); System.out.println("=".repeat(60)); + var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); + customTypes.put("BUG", "Known Bug"); var extension = AlertsExtension.builder() - .addCustomType("BUG", "Known Bug") + .setAllowedTypes(customTypes) .build(); var parser = Parser.builder() From 55004c83bc92fd6335a7a21d911f54069c65080c Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Sat, 6 Jun 2026 21:06:34 -0700 Subject: [PATCH 301/345] Revert "Remove addCustomType" This reverts commit 9c813f6053400b06ce3dcb267c2b4111f3ea8e34. --- CHANGELOG.md | 14 ++------ commonmark-ext-gfm-alerts/README.md | 19 ++++++++--- .../ext/gfm/alerts/AlertsExtension.java | 24 ++++++++++++++ .../alerts/AlertsMarkdownRendererTest.java | 33 +++++++++---------- .../commonmark/ext/gfm/alerts/AlertsTest.java | 31 ++++++----------- .../gfm/alerts/examples/AlertsExample.java | 5 +-- 6 files changed, 66 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9fd67a41..8f725d366 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,9 +20,8 @@ with the exception that 0.x versions can break between minor versions. other blocks (including other alerts). See [this section of the alerts README](./commonmark-ext-gfm-alerts/README.md#nesting-alerts) for more information. - - **Breaking:** `AlertsExtension.Builder#addCustomType` has been changed to - `setAllowedTypes` to allow the set of alert types (including standard GFM types) - to be completely overwritten. + - New configuration for `AlertsExtension` to allow the set of alert types + (including standard GFM types) to be completely overwritten. ```java var extension = AlertsExtension.builder() .setAllowedTypes(Map.ofEntries( @@ -32,15 +31,6 @@ with the exception that 0.x versions can break between minor versions. )) .build(); ``` - Example migration: - ```diff - + var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); - + customTypes.put("BUG", "Known Bug"); - var extension = AlertsExtension.builder() - - .addCustomType("BUG", "Known Bug") - + .setAllowedTypes(customTypes) - .build(); - ``` ## [0.28.0] - 2026-03-31 ### Added diff --git a/commonmark-ext-gfm-alerts/README.md b/commonmark-ext-gfm-alerts/README.md index c61987c76..fddff7f0d 100644 --- a/commonmark-ext-gfm-alerts/README.md +++ b/commonmark-ext-gfm-alerts/README.md @@ -26,19 +26,28 @@ var renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); ### Custom Alert Types -By default, the five standard GFM alert types are used. These can be added to or replaced -with a new set of alert types: +Add custom types beyond the five standard GFM types: ```java -var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); -customTypes.put("BUG", "Known Bug"); var extension = AlertsExtension.builder() - .setAllowedTypes(customTypes) + .addCustomType("BUG", "Known Bug") .build(); ``` Custom types must be UPPERCASE. Standard type titles can also be overridden for localization. +The allowed types (including the five standard GFM types) can also be completely overwritten: + +```java +var extension = AlertsExtension.builder() + .setAllowedTypes(Map.ofEntries( + Map.entry("IMPORTANT", "Important"), + Map.entry("WARNING", "Warning") + Map.entry("BUG", "Known Bug") + )) + .build(); +``` + ### Custom Alert Titles Allow authors to provide custom titles per alert by adding text after the alert diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index 48a09e02d..e95a51eb1 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -154,6 +154,30 @@ public Builder setAllowedTypes(Map allowedTypes) { return this; } + /** + * Adds a custom alert type with a default title. + *

    + * This can also be used to override the default title of standard GFM types + * (e.g., for localization). + * + * @param type the alert type (must be uppercase) + * @param title the default title for this alert type + * @return {@code this} + */ + public Builder addCustomType(String type, String title) { + if (type == null || type.isEmpty()) { + throw new IllegalArgumentException("Type must not be null or empty"); + } + if (title == null || title.isEmpty()) { + throw new IllegalArgumentException("Title must not be null or empty"); + } + if (!type.equals(type.toUpperCase(Locale.ROOT))) { + throw new IllegalArgumentException("Type must be uppercase: " + type); + } + allowedTypes.put(type, title); + return this; + } + /** * Allows or disallows custom titles on alerts. Inline formatting is supported * within these titles. diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java index 1409dc8ef..f9deb8dd6 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java @@ -5,7 +5,6 @@ import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.jupiter.api.Test; -import java.util.HashMap; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -41,43 +40,41 @@ public void allStandardTypesRoundTrip() { @Test public void lowercaseTypeRendersAsUppercase() { // Lowercase input gets normalized to uppercase type - var rendered = RENDERER.render(PARSER.parse("> [!note]\n> Content\n")); + String rendered = RENDERER.render(PARSER.parse("> [!note]\n> Content\n")); assertThat(rendered).isEqualTo("> [!NOTE]\n> Content\n"); } @Test public void leadingAndTrailingLinesAreRemoved() { - var rendered = RENDERER.render(PARSER.parse(">\n> \n>[!NOTE]\n> Content\n>\n> \n")); + String rendered = RENDERER.render(PARSER.parse(">\n> \n>[!NOTE]\n> Content\n>\n> \n")); assertThat(rendered).isEqualTo("> [!NOTE]\n> Content\n"); } @Test public void alertWithMultipleParagraphs() { - var input = "> [!NOTE]\n> First paragraph\n>\n> Second paragraph\n"; + String input = "> [!NOTE]\n> First paragraph\n>\n> Second paragraph\n"; // MarkdownWriter always writes the prefix including trailing space - var expected = "> [!NOTE]\n> First paragraph\n> \n> Second paragraph\n"; - var rendered = RENDERER.render(PARSER.parse(input)); + String expected = "> [!NOTE]\n> First paragraph\n> \n> Second paragraph\n"; + String rendered = RENDERER.render(PARSER.parse(input)); assertThat(rendered).isEqualTo(expected); } @Test public void customTypeRoundTrip() { - var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); - customTypes.put("INFO", "Information"); - var extension = AlertsExtension.builder() - .setAllowedTypes(customTypes) + Extension extension = AlertsExtension.builder() + .addCustomType("INFO", "Information") .build(); - var parser = Parser.builder().extensions(Set.of(extension)).build(); - var renderer = MarkdownRenderer.builder().extensions(Set.of(extension)).build(); - var input = "> [!INFO]\n> Custom type\n"; + Parser parser = Parser.builder().extensions(Set.of(extension)).build(); + MarkdownRenderer renderer = MarkdownRenderer.builder().extensions(Set.of(extension)).build(); + String input = "> [!INFO]\n> Custom type\n"; assertRoundTrip(input, parser, renderer); } @Test public void alertWithList() { - var input = "> [!NOTE]\n> Items:\n> \n> - First\n> - Second\n"; + String input = "> [!NOTE]\n> Items:\n> \n> - First\n> - Second\n"; assertRoundTrip(input); } @@ -95,17 +92,17 @@ public void customTitleWithFormattingRoundTrip() { @Test public void customTitleWithMultipleBlocks() { - var input = "> [!NOTE]Title\n> First paragraph\n>\n> Second paragraph\n>\n> - > Nested blocks\n"; + String input = "> [!NOTE]Title\n> First paragraph\n>\n> Second paragraph\n>\n> - > Nested blocks\n"; // MarkdownWriter always writes the prefix including trailing space - var expected = "> [!NOTE] Title\n> First paragraph\n> \n> Second paragraph\n> \n> - > Nested blocks\n"; - var rendered = RENDERER_CUSTOM_TITLES.render(PARSER_CUSTOM_TITLES.parse(input)); + String expected = "> [!NOTE] Title\n> First paragraph\n> \n> Second paragraph\n> \n> - > Nested blocks\n"; + String rendered = RENDERER_CUSTOM_TITLES.render(PARSER_CUSTOM_TITLES.parse(input)); assertThat(rendered).isEqualTo(expected); } // Helpers private void assertRoundTrip(String input, Parser parser, MarkdownRenderer renderer) { - var rendered = renderer.render(parser.parse(input)); + String rendered = renderer.render(parser.parse(input)); assertThat(rendered).isEqualTo(input); } diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java index 0b1b08e67..a029a6a9d 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -11,7 +11,6 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -47,10 +46,8 @@ private void assertRenderingCustomTitles(String source, String expectedResult) { @Test public void customType() { - var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); - customTypes.put("INFO", "Information"); var extension = AlertsExtension.builder() - .setAllowedTypes(customTypes) + .addCustomType("INFO", "Information") .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); @@ -66,11 +63,9 @@ public void customType() { @Test public void multipleCustomTypes() { var extension = AlertsExtension.builder() - .setAllowedTypes(Map.ofEntries( - Map.entry("INFO", "Information"), - Map.entry("SUCCESS", "Success!"), - Map.entry("DANGER", "Danger!") - )) + .addCustomType("INFO", "Information") + .addCustomType("SUCCESS", "Success!") + .addCustomType("DANGER", "Danger!") .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); @@ -93,10 +88,8 @@ public void multipleCustomTypes() { @Test public void standardTypesWithCustomConfigured() { - var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); - customTypes.put("INFO", "Information"); var extension = AlertsExtension.builder() - .setAllowedTypes(customTypes) + .addCustomType("INFO", "Information") .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); @@ -111,10 +104,8 @@ public void standardTypesWithCustomConfigured() { @Test public void overrideStandardTypeTitle() { - var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); - customTypes.put("NOTE", "Nota"); var extension = AlertsExtension.builder() - .setAllowedTypes(customTypes) + .addCustomType("NOTE", "Nota") .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); @@ -132,19 +123,19 @@ public void overrideStandardTypeTitle() { @Test public void customTypeMustBeUppercase() { assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().setAllowedTypes(Map.of("info", "Information")).build()); + AlertsExtension.builder().addCustomType("info", "Information").build()); } @Test public void customTypeMustNotBeEmpty() { assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().setAllowedTypes(Map.of("", "Title")).build()); + AlertsExtension.builder().addCustomType("", "Title").build()); } @Test public void customTypeTitleMustNotBeEmpty() { assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().setAllowedTypes(Map.of("INFO", "")).build()); + AlertsExtension.builder().addCustomType("INFO", "").build()); } @Test @@ -440,10 +431,8 @@ public void alertParsedAsAlertNode() { @Test public void customTypeParsedAsAlertNode() { - var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); - customTypes.put("INFO", "Information"); var extension = AlertsExtension.builder() - .setAllowedTypes(customTypes) + .addCustomType("INFO", "Information") .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java index 28969ac5c..34b78385c 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java @@ -4,7 +4,6 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; -import java.util.HashMap; import java.util.List; /** @@ -56,10 +55,8 @@ private static void customTypesExample() { System.out.println("CUSTOM ALERT TYPES"); System.out.println("=".repeat(60)); - var customTypes = new HashMap<>(AlertsExtension.STANDARD_TYPES); - customTypes.put("BUG", "Known Bug"); var extension = AlertsExtension.builder() - .setAllowedTypes(customTypes) + .addCustomType("BUG", "Known Bug") .build(); var parser = Parser.builder() From 8cb6a9846cda8378cbefca7a3bba0a7513d6b21b Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Sat, 6 Jun 2026 21:08:04 -0700 Subject: [PATCH 302/345] Use var instead of explicit types in AlertsMarkdownRendererTest --- .../alerts/AlertsMarkdownRendererTest.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java index f9deb8dd6..3ad6789ec 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java @@ -40,41 +40,41 @@ public void allStandardTypesRoundTrip() { @Test public void lowercaseTypeRendersAsUppercase() { // Lowercase input gets normalized to uppercase type - String rendered = RENDERER.render(PARSER.parse("> [!note]\n> Content\n")); + var rendered = RENDERER.render(PARSER.parse("> [!note]\n> Content\n")); assertThat(rendered).isEqualTo("> [!NOTE]\n> Content\n"); } @Test public void leadingAndTrailingLinesAreRemoved() { - String rendered = RENDERER.render(PARSER.parse(">\n> \n>[!NOTE]\n> Content\n>\n> \n")); + var rendered = RENDERER.render(PARSER.parse(">\n> \n>[!NOTE]\n> Content\n>\n> \n")); assertThat(rendered).isEqualTo("> [!NOTE]\n> Content\n"); } @Test public void alertWithMultipleParagraphs() { - String input = "> [!NOTE]\n> First paragraph\n>\n> Second paragraph\n"; + var input = "> [!NOTE]\n> First paragraph\n>\n> Second paragraph\n"; // MarkdownWriter always writes the prefix including trailing space - String expected = "> [!NOTE]\n> First paragraph\n> \n> Second paragraph\n"; - String rendered = RENDERER.render(PARSER.parse(input)); + var expected = "> [!NOTE]\n> First paragraph\n> \n> Second paragraph\n"; + var rendered = RENDERER.render(PARSER.parse(input)); assertThat(rendered).isEqualTo(expected); } @Test public void customTypeRoundTrip() { - Extension extension = AlertsExtension.builder() + var extension = AlertsExtension.builder() .addCustomType("INFO", "Information") .build(); - Parser parser = Parser.builder().extensions(Set.of(extension)).build(); - MarkdownRenderer renderer = MarkdownRenderer.builder().extensions(Set.of(extension)).build(); - String input = "> [!INFO]\n> Custom type\n"; + var parser = Parser.builder().extensions(Set.of(extension)).build(); + var renderer = MarkdownRenderer.builder().extensions(Set.of(extension)).build(); + var input = "> [!INFO]\n> Custom type\n"; assertRoundTrip(input, parser, renderer); } @Test public void alertWithList() { - String input = "> [!NOTE]\n> Items:\n> \n> - First\n> - Second\n"; + var input = "> [!NOTE]\n> Items:\n> \n> - First\n> - Second\n"; assertRoundTrip(input); } @@ -92,17 +92,17 @@ public void customTitleWithFormattingRoundTrip() { @Test public void customTitleWithMultipleBlocks() { - String input = "> [!NOTE]Title\n> First paragraph\n>\n> Second paragraph\n>\n> - > Nested blocks\n"; + var input = "> [!NOTE]Title\n> First paragraph\n>\n> Second paragraph\n>\n> - > Nested blocks\n"; // MarkdownWriter always writes the prefix including trailing space - String expected = "> [!NOTE] Title\n> First paragraph\n> \n> Second paragraph\n> \n> - > Nested blocks\n"; - String rendered = RENDERER_CUSTOM_TITLES.render(PARSER_CUSTOM_TITLES.parse(input)); + var expected = "> [!NOTE] Title\n> First paragraph\n> \n> Second paragraph\n> \n> - > Nested blocks\n"; + var rendered = RENDERER_CUSTOM_TITLES.render(PARSER_CUSTOM_TITLES.parse(input)); assertThat(rendered).isEqualTo(expected); } // Helpers private void assertRoundTrip(String input, Parser parser, MarkdownRenderer renderer) { - String rendered = renderer.render(parser.parse(input)); + var rendered = renderer.render(parser.parse(input)); assertThat(rendered).isEqualTo(input); } From 50d75cba868cb200ccb3b70f2eaae4600dac086f Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Sat, 6 Jun 2026 21:18:16 -0700 Subject: [PATCH 303/345] Fix bug where addCustomType would fail if setAllowedTypes was called prior --- .../commonmark/ext/gfm/alerts/AlertsExtension.java | 2 +- .../org/commonmark/ext/gfm/alerts/AlertsTest.java | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index e95a51eb1..1dbbd1d97 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -150,7 +150,7 @@ public Builder setAllowedTypes(Map allowedTypes) { } } - this.allowedTypes = Map.copyOf(allowedTypes); + this.allowedTypes = new HashMap<>(allowedTypes); return this; } diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java index a029a6a9d..20d61e8f4 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -141,7 +141,10 @@ public void customTypeTitleMustNotBeEmpty() { @Test public void removeStandardTypes() { var allowedTypes = Map.ofEntries(Map.entry("IMPORTANT", "Important")); - var extension = AlertsExtension.builder().setAllowedTypes(allowedTypes).build(); + var extension = AlertsExtension.builder() + .setAllowedTypes(allowedTypes) + .addCustomType("BUG", "Known Bug") + .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); @@ -162,6 +165,12 @@ public void removeStandardTypes() { "

    Important

    \n" + "

    Alert

    \n" + "
    \n"); + + assertThat(renderer.render(parser.parse("> [!BUG]\n> Alert"))).isEqualTo( + "
    \n" + + "

    Known Bug

    \n" + + "

    Alert

    \n" + + "
    \n"); } // Custom titles From 248931bb49e113d5479646f1b71f754b16656f18 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Sat, 6 Jun 2026 21:26:29 -0700 Subject: [PATCH 304/345] Share type and title validation logic --- .../ext/gfm/alerts/AlertsExtension.java | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index 1dbbd1d97..ca48f6229 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -128,6 +128,7 @@ public static class Builder { * Must not be null/empty or contain any null/empty keys or * values. Additionally, all alert types must be uppercase. * @return {@code this} + * @see Builder#addCustomType(String, String) */ public Builder setAllowedTypes(Map allowedTypes) { Objects.requireNonNull(allowedTypes, "allowedTypes must not be null"); @@ -136,18 +137,7 @@ public Builder setAllowedTypes(Map allowedTypes) { } for (Map.Entry entry : allowedTypes.entrySet()) { - var type = Objects.requireNonNull(entry.getKey(), "Types must not be null"); - if (type.isEmpty()) { - throw new IllegalArgumentException("Types must not be empty"); - } - if (!type.equals(type.toUpperCase(Locale.ROOT))) { - throw new IllegalArgumentException("Types must be uppercase: " + type); - } - - var defaultTitle = Objects.requireNonNull(entry.getValue(), "Default titles must not be null: " + type); - if (defaultTitle.isEmpty()) { - throw new IllegalArgumentException("Default titles must not be empty: " + type); - } + validateTypeAndTitle(entry.getKey(), entry.getValue()); } this.allowedTypes = new HashMap<>(allowedTypes); @@ -163,17 +153,10 @@ public Builder setAllowedTypes(Map allowedTypes) { * @param type the alert type (must be uppercase) * @param title the default title for this alert type * @return {@code this} + * @see Builder#setAllowedTypes(Map) */ public Builder addCustomType(String type, String title) { - if (type == null || type.isEmpty()) { - throw new IllegalArgumentException("Type must not be null or empty"); - } - if (title == null || title.isEmpty()) { - throw new IllegalArgumentException("Title must not be null or empty"); - } - if (!type.equals(type.toUpperCase(Locale.ROOT))) { - throw new IllegalArgumentException("Type must be uppercase: " + type); - } + validateTypeAndTitle(type, title); allowedTypes.put(type, title); return this; } @@ -214,5 +197,29 @@ public Builder allowNestedAlerts(boolean allow) { public Extension build() { return new AlertsExtension(this); } + + /** + * Checks whether an alert type and default title are valid. + * + * @param type The type to validate: + *

    + * - Must not be null or empty + * - Must be uppercase + * @param title The default title to validate. Must not be null or empty. + */ + private void validateTypeAndTitle(String type, String title) { + Objects.requireNonNull(type, "Type must not be null"); + if (type.isEmpty()) { + throw new IllegalArgumentException("Type must not be empty"); + } + if (!type.equals(type.toUpperCase(Locale.ROOT))) { + throw new IllegalArgumentException("Type must be uppercase: " + type); + } + + Objects.requireNonNull(title, "Default title must not be null: " + type); + if (title.isEmpty()) { + throw new IllegalArgumentException("Default title must not be empty: " + type); + } + } } } From fa45a9449a475c025e3b501f604467c066d5b9a6 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Sat, 6 Jun 2026 21:33:04 -0700 Subject: [PATCH 305/345] Add Tests for Validation --- .../commonmark/ext/gfm/alerts/AlertsTest.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java index 20d61e8f4..d98e0e2cf 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -138,8 +138,10 @@ public void customTypeTitleMustNotBeEmpty() { AlertsExtension.builder().addCustomType("INFO", "").build()); } + // Overwriting types + @Test - public void removeStandardTypes() { + public void overwriteStandardTypes() { var allowedTypes = Map.ofEntries(Map.entry("IMPORTANT", "Important")); var extension = AlertsExtension.builder() .setAllowedTypes(allowedTypes) @@ -173,6 +175,29 @@ public void removeStandardTypes() { "\n"); } + // Overwriting types validation + + @Test + public void overwriteTypesMustBeUppercase() { + var allowedTypes = Map.ofEntries(Map.entry("info", "Info")); + assertThrows(IllegalArgumentException.class, () -> + AlertsExtension.builder().setAllowedTypes(allowedTypes).build()); + } + + @Test + public void overwriteTypesMustNotBeEmpty() { + var allowedTypes = Map.ofEntries(Map.entry("", "Info")); + assertThrows(IllegalArgumentException.class, () -> + AlertsExtension.builder().setAllowedTypes(allowedTypes).build()); + } + + @Test + public void overwriteTypesTitleMustNotBeEmpty() { + var allowedTypes = Map.ofEntries(Map.entry("INFO", "")); + assertThrows(IllegalArgumentException.class, () -> + AlertsExtension.builder().setAllowedTypes(allowedTypes).build()); + } + // Custom titles @Test From 717dedd60df646894518540102d88486371a87f3 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Wed, 10 Jun 2026 01:13:03 -0700 Subject: [PATCH 306/345] Add Text Content Rendering Support for Alerts --- CHANGELOG.md | 14 +- .../org/commonmark/ext/gfm/alerts/Alert.java | 39 ++++ .../ext/gfm/alerts/AlertsExtension.java | 9 +- .../internal/AlertHtmlNodeRenderer.java | 27 +-- .../internal/AlertMarkdownNodeRenderer.java | 21 +-- .../alerts/internal/AlertNodeRenderer.java | 28 +++ .../AlertTextContentNodeRenderer.java | 54 ++++++ .../alerts/AlertsMarkdownRendererTest.java | 24 +++ .../alerts/AlertsTextContentRendererTest.java | 167 ++++++++++++++++++ 9 files changed, 343 insertions(+), 40 deletions(-) create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertTextContentNodeRenderer.java create mode 100644 commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTextContentRendererTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f725d366..38d9a0535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,20 +8,20 @@ with the exception that 0.x versions can break between minor versions. ## [Unreleased] ### Added -- Support rendering GFM task list items to Markdown -- Support rendering YAML front matter to Markdown +- Support rendering GFM task list items to Markdown (#433) +- Support rendering YAML front matter to Markdown (#434) - Alerts - - Allow customizing HTML attributes for alert title `

    ` tag via `AttributeProvider` + - Allow customizing HTML attributes for alert title `

    ` tag via `AttributeProvider` (#427) - New configuration for `AlertsExtension` to allow authors to provide custom titles per alert. See the [custom titles section of the alerts README](./commonmark-ext-gfm-alerts/README.md#custom-alert-titles) - for more information. + for more information. (#430) - New configuration for `AlertsExtension` to allow alerts to be nested within other blocks (including other alerts). See [this section of the alerts README](./commonmark-ext-gfm-alerts/README.md#nesting-alerts) - for more information. + for more information. (#430) - New configuration for `AlertsExtension` to allow the set of alert types - (including standard GFM types) to be completely overwritten. + (including standard GFM types) to be completely overwritten. (#435) ```java var extension = AlertsExtension.builder() .setAllowedTypes(Map.ofEntries( @@ -31,6 +31,8 @@ with the exception that 0.x versions can break between minor versions. )) .build(); ``` + - Support rendering alerts to text (#437) + ## [0.28.0] - 2026-03-31 ### Added diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java index b95f697ed..71c35d24d 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java @@ -18,4 +18,43 @@ public Alert(String type) { public String getType() { return type; } + + /** + * @return Whether this alert has any body content (not including the title). + *

    + * + * - Examples where this would be {@code true}: + *

    {@code
    +     *   > [!NOTE]
    +     *   > Body text
    +     *   }
    + *
    {@code
    +     *   > [!NOTE] Custom title
    +     *   > Body text
    +     *   }
    + * + * - Examples where this would be {@code false}: + * + *
    {@code
    +     *   > [!NOTE]
    +     *   }
    + *
    {@code
    +     *   > [!NOTE]
    +     *   >
    +     *   >
    +     *   }
    + *
    {@code
    +     *   > [!NOTE] Custom title
    +     *   }
    + */ + public boolean hasBody() { + var first = this.getFirstChild(); + if (first instanceof AlertTitle) { + // Body exists if there's a sibling after AlertTitle + return first.getNext() != null; + } else { + // Body exists if there are any children + return first != null; + } + } } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index ca48f6229..ed81a2c3f 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -4,12 +4,14 @@ import org.commonmark.ext.gfm.alerts.internal.AlertBlockParser; import org.commonmark.ext.gfm.alerts.internal.AlertHtmlNodeRenderer; import org.commonmark.ext.gfm.alerts.internal.AlertMarkdownNodeRenderer; +import org.commonmark.ext.gfm.alerts.internal.AlertTextContentNodeRenderer; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; import org.commonmark.renderer.markdown.MarkdownRenderer; +import org.commonmark.renderer.text.TextContentRenderer; import java.util.HashMap; import java.util.Locale; @@ -48,7 +50,7 @@ * }
    */ public class AlertsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, - MarkdownRenderer.MarkdownRendererExtension { + TextContentRenderer.TextContentRendererExtension, MarkdownRenderer.MarkdownRendererExtension { /** * The standard GitHub Flavored Markdown (GFM) types that the extension @@ -95,6 +97,11 @@ public void extend(HtmlRenderer.Builder rendererBuilder) { rendererBuilder.nodeRendererFactory(context -> new AlertHtmlNodeRenderer(context, allowedTypes)); } + @Override + public void extend(TextContentRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(context -> new AlertTextContentNodeRenderer(context, allowedTypes)); + } + @Override public void extend(MarkdownRenderer.Builder rendererBuilder) { rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java index be337d5be..bb791a93b 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -24,6 +24,9 @@ public AlertHtmlNodeRenderer(HtmlNodeRendererContext context, Map allowedTypes; + + public AlertTextContentNodeRenderer(TextContentNodeRendererContext context, Map allowedTypes) { + this.context = context; + this.textContent = context.getWriter(); + this.allowedTypes = allowedTypes; + } + + @Override + protected void renderAlert(Alert alert) { + var type = alert.getType(); + if (!allowedTypes.containsKey(type)) { + throw new IllegalStateException("Unknown alert type: " + type); + } + + var first = alert.getFirstChild(); + if (first instanceof AlertTitle) { + renderChildren(first); + } else { + textContent.write(allowedTypes.get(type)); + } + + if (alert.hasBody()) { + if (context.lineBreakRendering() == LineBreakRendering.STRIP) { + textContent.write(": "); + } else { + textContent.block(); + } + renderChildren(alert); + } + + textContent.block(); + } + + @Override + protected void renderNode(Node node) { + context.render(node); + } +} diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java index 3ad6789ec..e31059ed2 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java @@ -78,6 +78,18 @@ public void alertWithList() { assertRoundTrip(input); } + @Test + public void alertNoBody() { + var input = "> [!NOTE]\n"; + assertRoundTrip(input); + } + + @Test + public void alertNoBodyOtherBlocks() { + var input = "- List\n\n> [!NOTE]\n\n- List\n"; + assertRoundTrip(input); + } + // Custom titles @Test @@ -99,6 +111,18 @@ public void customTitleWithMultipleBlocks() { assertThat(rendered).isEqualTo(expected); } + @Test + public void customTitleNoBody() { + var input = "> [!WARNING] Custom title\n"; + assertRoundTripCustomTitles(input); + } + + @Test + public void customTitleNoBodyOtherBlocks() { + var input = "1. Ordered list\n\n> [!WARNING] Custom title\n\n1. Ordered list\n"; + assertRoundTripCustomTitles(input); + } + // Helpers private void assertRoundTrip(String input, Parser parser, MarkdownRenderer renderer) { diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTextContentRendererTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTextContentRendererTest.java new file mode 100644 index 000000000..9bc69677c --- /dev/null +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTextContentRendererTest.java @@ -0,0 +1,167 @@ +package org.commonmark.ext.gfm.alerts; + +import org.commonmark.Extension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.text.LineBreakRendering; +import org.commonmark.renderer.text.TextContentRenderer; +import org.commonmark.testutil.Asserts; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +public class AlertsTextContentRendererTest { + + private static final Set EXTENSIONS = Set.of(AlertsExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + + private static final TextContentRenderer COMPACT_RENDERER = TextContentRenderer.builder() + .extensions(EXTENSIONS).build(); + private static final TextContentRenderer SEPARATE_RENDERER = TextContentRenderer.builder() + .extensions(EXTENSIONS).lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS).build(); + private static final TextContentRenderer STRIPPED_RENDERER = TextContentRenderer.builder() + .extensions(EXTENSIONS).lineBreakRendering(LineBreakRendering.STRIP).build(); + + private static final Set EXTENSIONS_CUSTOM_TITLES = Set.of( + AlertsExtension.builder().allowCustomTitles(true).allowNestedAlerts(true).build()); + private static final Parser PARSER_CUSTOM_TITLES = Parser.builder() + .extensions(EXTENSIONS_CUSTOM_TITLES).build(); + + private static final TextContentRenderer COMPACT_RENDERER_CUSTOM = TextContentRenderer.builder() + .extensions(EXTENSIONS_CUSTOM_TITLES).build(); + private static final TextContentRenderer SEPARATE_RENDERER_CUSTOM = TextContentRenderer.builder() + .extensions(EXTENSIONS_CUSTOM_TITLES) + .lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS).build(); + private static final TextContentRenderer STRIPPED_RENDERER_CUSTOM = TextContentRenderer.builder() + .extensions(EXTENSIONS_CUSTOM_TITLES) + .lineBreakRendering(LineBreakRendering.STRIP).build(); + + @Test + public void alertNoBody() { + var source = "> [!NOTE]"; + assertCompact(source, "Note"); + assertSeparate(source, "Note"); + assertStripped(source, "Note"); + } + + @Test + public void alertWithBody() { + var source = "> [!NOTE]\n> Body text"; + assertCompact(source, "Note\nBody text"); + assertSeparate(source, "Note\n\nBody text"); + assertStripped(source, "Note: Body text"); + } + + @Test + public void alertWithMultilineBody() { + var source = "> [!WARNING]\n> First line\n> Second line"; + assertCompact(source, "Warning\nFirst line\nSecond line"); + assertSeparate(source, "Warning\n\nFirst line\nSecond line"); + assertStripped(source, "Warning: First line Second line"); + } + + @Test + public void alertWithParagraphBody() { + var source = "> [!IMPORTANT]\n>\n> First paragraph\n>\n> Second paragraph"; + assertCompact(source, "Important\nFirst paragraph\nSecond paragraph"); + assertSeparate(source, "Important\n\nFirst paragraph\n\nSecond paragraph"); + assertStripped(source, "Important: First paragraph Second paragraph"); + } + + @Test + public void multipleAlerts() { + var source = "> [!NOTE]\n> First\n\n> [!TIP]\n> Second"; + assertCompact(source, "Note\nFirst\nTip\nSecond"); + assertSeparate(source, "Note\n\nFirst\n\nTip\n\nSecond"); + assertStripped(source, "Note: First Tip: Second"); + } + + @Test + public void alertWithInlineFormatting() { + var source = "> [!NOTE]\n> This has *emphasis* and **strong**"; + assertCompact(source, "Note\nThis has emphasis and strong"); + assertSeparate(source, "Note\n\nThis has emphasis and strong"); + assertStripped(source, "Note: This has emphasis and strong"); + } + + // Custom titles + + @Test + public void alertWithCustomTitle() { + var source = "> [!NOTE] Custom title\n> This has *emphasis* and **strong**"; + assertCompactCustomTitles(source, "Custom title\nThis has emphasis and strong"); + assertSeparateCustomTitles(source, "Custom title\n\nThis has emphasis and strong"); + assertStrippedCustomTitles(source, "Custom title: This has emphasis and strong"); + } + + @Test + public void alertWithCustomTitleNoBody() { + var source = "> [!NOTE] Custom _title_"; + assertCompactCustomTitles(source, "Custom title"); + assertSeparateCustomTitles(source, "Custom title"); + assertStrippedCustomTitles(source, "Custom title"); + } + + // Nested alerts + + @Test + public void alertWithinList() { + var source = "- > [!WARNING]\n > This has _emphasis_ and `code`"; + assertCompactCustomTitles(source, "- Warning\n This has emphasis and \"code\""); + assertSeparateCustomTitles(source, "- Warning\n This has emphasis and \"code\""); + assertStrippedCustomTitles(source, "Warning: This has emphasis and \"code\""); + } + + @Test + public void alertWithinBlockQuote() { + var source = ">\n> > [!WARNING]\n> > This has _emphasis_ and **strong**"; + assertCompactCustomTitles(source, "«Warning\nThis has emphasis and strong»"); + assertSeparateCustomTitles(source, "«Warning\n\nThis has emphasis and strong»"); + assertStrippedCustomTitles(source, "«Warning: This has emphasis and strong»"); + } + + @Test + public void alertWithinAlert() { + var source = "> [!NOTE] Custom title\n> Body text\n> > [!WARNING]\n> > Nested body text"; + assertCompactCustomTitles(source, "Custom title\nBody text\nWarning\nNested body text"); + assertSeparateCustomTitles(source, "Custom title\n\nBody text\n\nWarning\n\nNested body text"); + assertStrippedCustomTitles(source, "Custom title: Body text Warning: Nested body text"); + } + + // Helpers + + private void assertCompact(String source, String expected) { + var doc = PARSER.parse(source); + var actualRendering = COMPACT_RENDERER.render(doc); + Asserts.assertRendering(source, expected, actualRendering); + } + + private void assertSeparate(String source, String expected) { + var doc = PARSER.parse(source); + var actualRendering = SEPARATE_RENDERER.render(doc); + Asserts.assertRendering(source, expected, actualRendering); + } + + private void assertStripped(String source, String expected) { + var doc = PARSER.parse(source); + var actualRendering = STRIPPED_RENDERER.render(doc); + Asserts.assertRendering(source, expected, actualRendering); + } + + private void assertCompactCustomTitles(String source, String expected) { + var doc = PARSER_CUSTOM_TITLES.parse(source); + var actualRendering = COMPACT_RENDERER_CUSTOM.render(doc); + Asserts.assertRendering(source, expected, actualRendering); + } + + private void assertSeparateCustomTitles(String source, String expected) { + var doc = PARSER_CUSTOM_TITLES.parse(source); + var actualRendering = SEPARATE_RENDERER_CUSTOM.render(doc); + Asserts.assertRendering(source, expected, actualRendering); + } + + private void assertStrippedCustomTitles(String source, String expected) { + var doc = PARSER_CUSTOM_TITLES.parse(source); + var actualRendering = STRIPPED_RENDERER_CUSTOM.render(doc); + Asserts.assertRendering(source, expected, actualRendering); + } +} From ffa682edf0a49ac4e87d9da95c94b033ead5b4e9 Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Sun, 14 Jun 2026 02:44:05 -0700 Subject: [PATCH 307/345] Update AlertTextContentNodeRenderer.java --- .../gfm/alerts/internal/AlertTextContentNodeRenderer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertTextContentNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertTextContentNodeRenderer.java index 61b3c7c28..c14ea36a4 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertTextContentNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertTextContentNodeRenderer.java @@ -24,7 +24,8 @@ public AlertTextContentNodeRenderer(TextContentNodeRendererContext context, Map< @Override protected void renderAlert(Alert alert) { var type = alert.getType(); - if (!allowedTypes.containsKey(type)) { + var defaultTitle = allowedTypes.get(type); + if (defaultTitle == null) { throw new IllegalStateException("Unknown alert type: " + type); } @@ -32,7 +33,7 @@ protected void renderAlert(Alert alert) { if (first instanceof AlertTitle) { renderChildren(first); } else { - textContent.write(allowedTypes.get(type)); + textContent.write(defaultTitle); } if (alert.hasBody()) { From 0117b82cbd30106e3af90802ec8bbca5aabc76ec Mon Sep 17 00:00:00 2001 From: Ryan DeStefano <67760716+rdestefa@users.noreply.github.com> Date: Sun, 14 Jun 2026 02:45:31 -0700 Subject: [PATCH 308/345] Update AlertHtmlNodeRenderer.java --- .../ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java index bb791a93b..6b9ba6da9 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -24,7 +24,8 @@ public AlertHtmlNodeRenderer(HtmlNodeRendererContext context, Map Date: Mon, 15 Jun 2026 21:46:41 -0700 Subject: [PATCH 309/345] Update CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38d9a0535..f4e22e14e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,6 @@ with the exception that 0.x versions can break between minor versions. ``` - Support rendering alerts to text (#437) - ## [0.28.0] - 2026-03-31 ### Added - New extension for alerts (aka callouts/admonitions) From c5f6bc9f761e5d8ed8c0cec484efc4dc0541dd07 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 20 Jun 2026 17:05:05 +1000 Subject: [PATCH 310/345] Prepare CHANGELOG for version 0.29.0 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4e22e14e..d1dc50874 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. -## [Unreleased] +## [0.29.0] - 2026-06-20 ### Added - Support rendering GFM task list items to Markdown (#433) - Support rendering YAML front matter to Markdown (#434) @@ -560,7 +560,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. -[Unreleased]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.28.0...main +[0.29.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.28.0...commonmark-parent-0.29.0 [0.28.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.1...commonmark-parent-0.28.0 [0.27.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.0...commonmark-parent-0.27.1 [0.27.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.26.0...commonmark-parent-0.27.0 From b0f4dbc849c8b0b75ad4d6770e8ba212b9229335 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 20 Jun 2026 17:05:14 +1000 Subject: [PATCH 311/345] mvn versions:set -DnewVersion=0.29.0-SNAPSHOT --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-alerts/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 26 ++++++++++++------------ 14 files changed, 26 insertions(+), 26 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 2cc4d53ca..6b9ea5377 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 0d9e2f30c..3eb0d8e29 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-alerts/pom.xml b/commonmark-ext-gfm-alerts/pom.xml index 02ecbf802..400b32394 100644 --- a/commonmark-ext-gfm-alerts/pom.xml +++ b/commonmark-ext-gfm-alerts/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-ext-gfm-alerts diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 9d8f55e5f..5e9f1d2ef 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 5bd323168..3becde04f 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 26d2d19b1..7e96f97cd 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index e646bc3fd..c9a19d727 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 48481c073..06bfe4748 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 4359f8707..533d63717 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index e6822f771..1fdb2f01f 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 7e0048a73..20328dc96 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 6a9c342cc..bb5bafa16 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 4e060edaa..ebe286c44 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index f12805316..2de80d374 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -133,62 +133,62 @@ org.commonmark commonmark - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT org.commonmark commonmark-ext-ins - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT org.commonmark commonmark-ext-gfm-alerts - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT org.commonmark commonmark-test-util - 0.28.1-SNAPSHOT + 0.29.0-SNAPSHOT From 57cf9fde68ae40efc3943557caccd3628bccc3cf Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Sat, 20 Jun 2026 07:18:46 +0000 Subject: [PATCH 312/345] [maven-release-plugin] prepare release commonmark-parent-0.29.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-alerts/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 28 ++++++++++++------------ 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 6b9ea5377..1c46396cd 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 3eb0d8e29..2b48ca135 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-alerts/pom.xml b/commonmark-ext-gfm-alerts/pom.xml index 400b32394..7e3eb5063 100644 --- a/commonmark-ext-gfm-alerts/pom.xml +++ b/commonmark-ext-gfm-alerts/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark-ext-gfm-alerts diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 5e9f1d2ef..39736e4d7 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 3becde04f..5d6a40b2f 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 7e96f97cd..2f463c987 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index c9a19d727..84a169d2b 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 06bfe4748..9c046ca24 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 533d63717..b5d8f1d0e 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 1fdb2f01f..df9b23e05 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.29.0-SNAPSHOT + 0.29.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 20328dc96..08274cfb5 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index bb5bafa16..297538028 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index ebe286c44..131fef33c 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark diff --git a/pom.xml b/pom.xml index 2de80d374..6fd647627 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.29.0-SNAPSHOT + 0.29.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -133,62 +133,62 @@ org.commonmark commonmark - 0.29.0-SNAPSHOT + 0.29.0 org.commonmark commonmark-ext-autolink - 0.29.0-SNAPSHOT + 0.29.0 org.commonmark commonmark-ext-footnotes - 0.29.0-SNAPSHOT + 0.29.0 org.commonmark commonmark-ext-image-attributes - 0.29.0-SNAPSHOT + 0.29.0 org.commonmark commonmark-ext-ins - 0.29.0-SNAPSHOT + 0.29.0 org.commonmark commonmark-ext-gfm-alerts - 0.29.0-SNAPSHOT + 0.29.0 org.commonmark commonmark-ext-gfm-strikethrough - 0.29.0-SNAPSHOT + 0.29.0 org.commonmark commonmark-ext-gfm-tables - 0.29.0-SNAPSHOT + 0.29.0 org.commonmark commonmark-ext-heading-anchor - 0.29.0-SNAPSHOT + 0.29.0 org.commonmark commonmark-ext-task-list-items - 0.29.0-SNAPSHOT + 0.29.0 org.commonmark commonmark-ext-yaml-front-matter - 0.29.0-SNAPSHOT + 0.29.0 org.commonmark commonmark-test-util - 0.29.0-SNAPSHOT + 0.29.0 @@ -315,7 +315,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - HEAD + commonmark-parent-0.29.0 From 43b8da8f564bc4c027a12c6e3cafb6911b64bb6c Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Sat, 20 Jun 2026 07:18:48 +0000 Subject: [PATCH 313/345] [maven-release-plugin] prepare for next development iteration --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-alerts/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 28 ++++++++++++------------ 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 1c46396cd..974b46c4d 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 2b48ca135..8b9e3e541 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-alerts/pom.xml b/commonmark-ext-gfm-alerts/pom.xml index 7e3eb5063..f7b2ad20f 100644 --- a/commonmark-ext-gfm-alerts/pom.xml +++ b/commonmark-ext-gfm-alerts/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark-ext-gfm-alerts diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 39736e4d7..0818eea0f 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 5d6a40b2f..9ba181caa 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 2f463c987..1aaca7607 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 84a169d2b..1b6918623 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 9c046ca24..e07128435 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index b5d8f1d0e..355182c49 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index df9b23e05..434cbe51f 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.29.0 + 0.29.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 08274cfb5..7bee5bf54 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 297538028..52b39a25f 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 131fef33c..c58fba42e 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 6fd647627..54edab537 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.29.0 + 0.29.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -133,62 +133,62 @@ org.commonmark commonmark - 0.29.0 + 0.29.1-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.29.0 + 0.29.1-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.29.0 + 0.29.1-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.29.0 + 0.29.1-SNAPSHOT org.commonmark commonmark-ext-ins - 0.29.0 + 0.29.1-SNAPSHOT org.commonmark commonmark-ext-gfm-alerts - 0.29.0 + 0.29.1-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.29.0 + 0.29.1-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.29.0 + 0.29.1-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.29.0 + 0.29.1-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.29.0 + 0.29.1-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.29.0 + 0.29.1-SNAPSHOT org.commonmark commonmark-test-util - 0.29.0 + 0.29.1-SNAPSHOT @@ -315,7 +315,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - commonmark-parent-0.29.0 + HEAD From 9477a93b6b0965efc54c55bd40ad88fbbe25bc6f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 20 Jun 2026 17:31:10 +1000 Subject: [PATCH 314/345] README: Bump version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1f00e0f01..8ee0b0119 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.28.0 + 0.29.0 ``` @@ -293,7 +293,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.28.0 + 0.29.0 ``` From e1d5292165d132057bd34236b10e5c02fe710043 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 28 Jul 2026 21:53:40 +1000 Subject: [PATCH 315/345] Use google-java-format (via spotless) Using AOSP style because: - It's 4 spaces indentation (instead of 2 for the default). 4 spaces matches the current style, so reformatting isn't going to change all lines. - It's also closer to OpenJDK's own style as commonmark-java is vendored there: https://github.com/openjdk/jdk/tree/master/src/jdk.internal.md/share/classes/jdk/internal/org/commonmark --- .idea/google-java-format.xml | 7 +++++++ pom.xml | 12 ++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 .idea/google-java-format.xml diff --git a/.idea/google-java-format.xml b/.idea/google-java-format.xml new file mode 100644 index 000000000..05946a82a --- /dev/null +++ b/.idea/google-java-format.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 54edab537..94c76c4ee 100644 --- a/pom.xml +++ b/pom.xml @@ -124,6 +124,18 @@ + + com.diffplug.spotless + spotless-maven-plugin + 3.9.0 + + + + + + + + From bb8c71fc9f070520b282016d69c3a2d4b072ecfd Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 29 Jul 2026 18:41:00 +1000 Subject: [PATCH 316/345] Format using `mvn spotless:apply` Had to reflow some multi-line comments, but other than that all good. --- .../ext/autolink/AutolinkExtension.java | 23 +- .../commonmark/ext/autolink/AutolinkType.java | 16 +- .../internal/AutolinkPostProcessor.java | 7 +- .../commonmark/ext/autolink/AutolinkTest.java | 74 +- .../ext/footnotes/FootnoteDefinition.java | 12 +- .../ext/footnotes/FootnoteReference.java | 7 +- .../ext/footnotes/FootnotesExtension.java | 64 +- .../ext/footnotes/InlineFootnote.java | 3 +- .../internal/FootnoteBlockParser.java | 34 +- .../internal/FootnoteHtmlNodeRenderer.java | 203 +-- .../internal/FootnoteLinkProcessor.java | 27 +- .../FootnoteMarkdownNodeRenderer.java | 3 +- .../footnotes/FootnoteHtmlRendererTest.java | 440 +++--- .../FootnoteMarkdownRendererTest.java | 20 +- .../ext/footnotes/FootnotesTest.java | 60 +- .../org/commonmark/ext/gfm/alerts/Alert.java | 45 +- .../commonmark/ext/gfm/alerts/AlertTitle.java | 14 +- .../ext/gfm/alerts/AlertsExtension.java | 158 +- .../gfm/alerts/internal/AlertBlockParser.java | 23 +- .../internal/AlertHtmlNodeRenderer.java | 11 +- .../alerts/internal/AlertNodeRenderer.java | 11 +- .../AlertTextContentNodeRenderer.java | 6 +- .../alerts/AlertsMarkdownRendererTest.java | 36 +- .../ext/gfm/alerts/AlertsSpecTest.java | 26 +- .../commonmark/ext/gfm/alerts/AlertsTest.java | 558 +++---- .../alerts/AlertsTextContentRendererTest.java | 60 +- .../gfm/alerts/examples/AlertsExample.java | 75 +- .../ext/gfm/strikethrough/Strikethrough.java | 4 +- .../strikethrough/StrikethroughExtension.java | 88 +- .../StrikethroughDelimiterProcessor.java | 5 +- .../StrikethroughHtmlNodeRenderer.java | 3 +- .../internal/StrikethroughNodeRenderer.java | 3 +- .../StrikethroughTextContentNodeRenderer.java | 4 +- .../StrikethroughMarkdownRendererTest.java | 10 +- .../strikethrough/StrikethroughSpecTest.java | 11 +- .../gfm/strikethrough/StrikethroughTest.java | 46 +- .../commonmark/ext/gfm/tables/TableBlock.java | 7 +- .../commonmark/ext/gfm/tables/TableBody.java | 7 +- .../commonmark/ext/gfm/tables/TableCell.java | 16 +- .../commonmark/ext/gfm/tables/TableHead.java | 7 +- .../commonmark/ext/gfm/tables/TableRow.java | 3 +- .../ext/gfm/tables/TablesExtension.java | 51 +- .../gfm/tables/internal/TableBlockParser.java | 41 +- .../internal/TableHtmlNodeRenderer.java | 6 +- .../internal/TableMarkdownNodeRenderer.java | 8 +- .../tables/internal/TableNodeRenderer.java | 6 +- .../TableTextContentNodeRenderer.java | 3 +- .../gfm/tables/TableMarkdownRendererTest.java | 13 +- .../ext/gfm/tables/TablesSpecTest.java | 11 +- .../commonmark/ext/gfm/tables/TablesTest.java | 1314 +++++++++-------- .../ext/gfm/tables/TablesTextContentTest.java | 25 +- .../anchor/HeadingAnchorExtension.java | 21 +- .../ext/heading/anchor/IdGenerator.java | 56 +- .../internal/HeadingIdAttributeProvider.java | 40 +- .../HeadingAnchorConfigurationTest.java | 28 +- .../ext/heading/anchor/HeadingAnchorTest.java | 47 +- .../ext/image/attributes/ImageAttributes.java | 7 +- .../attributes/ImageAttributesExtension.java | 18 +- .../ImageAttributesAttributeProvider.java | 29 +- .../ImageAttributesDelimiterProcessor.java | 13 +- .../image/attributes/ImageAttributesTest.java | 89 +- .../main/java/org/commonmark/ext/ins/Ins.java | 4 +- .../org/commonmark/ext/ins/InsExtension.java | 52 +- .../ins/internal/InsDelimiterProcessor.java | 3 +- .../ext/ins/internal/InsHtmlNodeRenderer.java | 3 +- .../ext/ins/internal/InsNodeRenderer.java | 3 +- .../ext/ins/InsMarkdownRendererTest.java | 10 +- .../java/org/commonmark/ext/ins/InsTest.java | 31 +- .../task/list/items/TaskListItemMarker.java | 4 +- .../list/items/TaskListItemsExtension.java | 41 +- .../TaskListItemHtmlNodeRenderer.java | 5 +- .../internal/TaskListItemPostProcessor.java | 17 +- .../TaskListItemMarkdownRendererTest.java | 7 +- .../task/list/items/TaskListItemsTest.java | 137 +- .../front/matter/YamlFrontMatterBlock.java | 3 +- .../matter/YamlFrontMatterExtension.java | 42 +- .../ext/front/matter/YamlFrontMatterNode.java | 3 +- .../front/matter/YamlFrontMatterVisitor.java | 9 +- .../internal/YamlFrontMatterBlockParser.java | 24 +- .../YamlFrontMatterMarkdownRendererTest.java | 68 +- .../ext/front/matter/YamlFrontMatterTest.java | 146 +- .../integration/IntegrationTests.java | 7 +- .../integration/BoundsIntegrationTest.java | 14 +- .../commonmark/integration/Extensions.java | 24 +- .../ExtensionsIntegrationTest.java | 30 +- .../MarkdownRendererIntegrationTest.java | 10 +- .../integration/PegDownBenchmark.java | 19 +- .../SourceSpanIntegrationTest.java | 13 +- .../integration/SpecIntegrationTest.java | 50 +- .../java/org/commonmark/ui/DingusApp.java | 49 +- .../java/org/commonmark/testutil/Asserts.java | 3 +- .../org/commonmark/testutil/SpecTestCase.java | 6 +- .../commonmark/testutil/TestResources.java | 7 +- .../commonmark/testutil/example/Example.java | 8 +- .../testutil/example/ExampleReader.java | 30 +- .../main/java/org/commonmark/Extension.java | 11 +- .../org/commonmark/internal/BlockContent.java | 1 - .../internal/BlockContinueImpl.java | 1 - .../commonmark/internal/BlockQuoteParser.java | 4 +- .../java/org/commonmark/internal/Bracket.java | 82 +- .../org/commonmark/internal/Definitions.java | 3 +- .../org/commonmark/internal/Delimiter.java | 20 +- .../internal/DocumentBlockParser.java | 1 - .../commonmark/internal/DocumentParser.java | 165 ++- .../internal/FencedCodeBlockParser.java | 26 +- .../commonmark/internal/HeadingParser.java | 22 +- .../commonmark/internal/HtmlBlockParser.java | 107 +- .../internal/IndentedCodeBlockParser.java | 13 +- .../internal/InlineParserContextImpl.java | 18 +- .../commonmark/internal/InlineParserImpl.java | 243 +-- .../LinkReferenceDefinitionParser.java | 96 +- .../commonmark/internal/ListBlockParser.java | 54 +- .../commonmark/internal/ListItemParser.java | 11 +- .../commonmark/internal/ParagraphParser.java | 6 +- .../internal/StaggeredDelimiterProcessor.java | 28 +- .../internal/ThematicBreakParser.java | 11 +- .../internal/inline/AutolinkInlineParser.java | 18 +- .../inline/BackslashInlineParser.java | 10 +- .../inline/BackticksInlineParser.java | 19 +- .../internal/inline/CoreLinkProcessor.java | 6 +- .../inline/EmphasisDelimiterProcessor.java | 6 +- .../internal/inline/EntityInlineParser.java | 16 +- .../internal/inline/HtmlInlineParser.java | 77 +- .../internal/renderer/NodeRendererMap.java | 8 +- .../commonmark/internal/util/Escaping.java | 67 +- .../internal/util/Html5Entities.java | 6 +- .../commonmark/internal/util/LineReader.java | 16 +- .../commonmark/internal/util/LinkScanner.java | 20 +- .../org/commonmark/node/AbstractVisitor.java | 10 +- .../main/java/org/commonmark/node/Block.java | 7 +- .../java/org/commonmark/node/BlockQuote.java | 5 +- .../java/org/commonmark/node/BulletList.java | 8 +- .../main/java/org/commonmark/node/Code.java | 8 +- .../java/org/commonmark/node/CustomBlock.java | 3 +- .../org/commonmark/node/DefinitionMap.java | 15 +- .../java/org/commonmark/node/Delimited.java | 4 +- .../java/org/commonmark/node/Document.java | 4 +- .../java/org/commonmark/node/Emphasis.java | 7 +- .../org/commonmark/node/FencedCodeBlock.java | 20 +- .../org/commonmark/node/HardLineBreak.java | 2 + .../java/org/commonmark/node/Heading.java | 4 +- .../main/java/org/commonmark/node/Image.java | 4 +- .../commonmark/node/IndentedCodeBlock.java | 2 + .../main/java/org/commonmark/node/Link.java | 26 +- .../node/LinkReferenceDefinition.java | 10 +- .../java/org/commonmark/node/ListBlock.java | 5 +- .../java/org/commonmark/node/ListItem.java | 50 +- .../main/java/org/commonmark/node/Node.java | 12 +- .../main/java/org/commonmark/node/Nodes.java | 8 +- .../java/org/commonmark/node/OrderedList.java | 12 +- .../org/commonmark/node/SoftLineBreak.java | 1 + .../java/org/commonmark/node/SourceSpan.java | 75 +- .../java/org/commonmark/node/SourceSpans.java | 8 +- .../org/commonmark/node/StrongEmphasis.java | 7 +- .../main/java/org/commonmark/node/Text.java | 9 +- .../org/commonmark/node/ThematicBreak.java | 1 + .../java/org/commonmark/node/Visitor.java | 4 +- .../org/commonmark/node/package-info.java | 3 +- .../java/org/commonmark/package-info.java | 9 +- .../commonmark/parser/IncludeSourceSpans.java | 16 +- .../org/commonmark/parser/InlineParser.java | 4 +- .../parser/InlineParserContext.java | 34 +- .../parser/InlineParserFactory.java | 7 +- .../java/org/commonmark/parser/Parser.java | 219 +-- .../org/commonmark/parser/PostProcessor.java | 1 - .../org/commonmark/parser/SourceLine.java | 6 +- .../org/commonmark/parser/SourceLines.java | 3 +- .../parser/beta/InlineContentParser.java | 20 +- .../beta/InlineContentParserFactory.java | 17 +- .../parser/beta/InlineParserState.java | 8 +- .../org/commonmark/parser/beta/LinkInfo.java | 42 +- .../commonmark/parser/beta/LinkProcessor.java | 28 +- .../commonmark/parser/beta/LinkResult.java | 26 +- .../commonmark/parser/beta/ParsedInline.java | 7 +- .../org/commonmark/parser/beta/Position.java | 4 +- .../org/commonmark/parser/beta/Scanner.java | 38 +- .../commonmark/parser/beta/package-info.java | 4 +- .../parser/block/AbstractBlockParser.java | 13 +- .../block/AbstractBlockParserFactory.java | 3 +- .../parser/block/BlockContinue.java | 8 +- .../commonmark/parser/block/BlockParser.java | 39 +- .../parser/block/BlockParserFactory.java | 6 +- .../commonmark/parser/block/BlockStart.java | 45 +- .../parser/block/MatchedBlockParser.java | 13 +- .../commonmark/parser/block/ParserState.java | 17 +- .../commonmark/parser/block/package-info.java | 4 +- .../parser/delimiter/DelimiterProcessor.java | 31 +- .../parser/delimiter/DelimiterRun.java | 28 +- .../org/commonmark/parser/package-info.java | 4 +- .../org/commonmark/renderer/NodeRenderer.java | 13 +- .../renderer/html/AttributeProvider.java | 28 +- .../html/AttributeProviderContext.java | 9 +- .../html/AttributeProviderFactory.java | 4 +- .../renderer/html/CoreHtmlNodeRenderer.java | 26 +- .../renderer/html/DefaultUrlSanitizer.java | 11 +- .../html/HtmlNodeRendererContext.java | 19 +- .../html/HtmlNodeRendererFactory.java | 4 +- .../renderer/html/HtmlRenderer.java | 80 +- .../commonmark/renderer/html/HtmlWriter.java | 3 +- .../renderer/html/UrlSanitizer.java | 9 +- .../renderer/html/package-info.java | 4 +- .../markdown/CoreMarkdownNodeRenderer.java | 199 ++- .../markdown/MarkdownNodeRendererContext.java | 12 +- .../markdown/MarkdownNodeRendererFactory.java | 11 +- .../renderer/markdown/MarkdownRenderer.java | 69 +- .../renderer/markdown/MarkdownWriter.java | 75 +- .../renderer/markdown/package-info.java | 4 +- .../text/CoreTextContentNodeRenderer.java | 6 +- .../renderer/text/LineBreakRendering.java | 15 +- .../text/TextContentNodeRendererContext.java | 13 +- .../text/TextContentNodeRendererFactory.java | 4 +- .../renderer/text/TextContentRenderer.java | 41 +- .../renderer/text/TextContentWriter.java | 36 +- .../renderer/text/package-info.java | 3 +- .../org/commonmark/text/AsciiMatcher.java | 4 +- .../java/org/commonmark/text/CharMatcher.java | 8 +- .../java/org/commonmark/text/Characters.java | 15 +- .../org/commonmark/text/package-info.java | 4 +- .../java/org/commonmark/ProfilingMain.java | 8 +- .../internal/DocumentParserTest.java | 51 +- .../LinkReferenceDefinitionParserTest.java | 14 +- .../internal/util/EscapingTest.java | 4 +- .../internal/util/LineReaderTest.java | 30 +- .../parser/InlineContentParserTest.java | 26 +- .../parser/beta/LinkProcessorTest.java | 14 +- .../commonmark/parser/beta/ScannerTest.java | 68 +- .../markdown/MarkdownRendererTest.java | 64 +- .../markdown/SpecMarkdownRendererTest.java | 25 +- .../commonmark/test/AbstractVisitorTest.java | 19 +- .../test/BlockParserFactoryTest.java | 35 +- .../org/commonmark/test/DelimitedTest.java | 38 +- .../test/DelimiterProcessorTest.java | 60 +- .../test/FencedCodeBlockParserTest.java | 29 +- .../org/commonmark/test/HtmlRendererTest.java | 256 ++-- .../test/InlineParserContextTest.java | 81 +- .../test/LinkReferenceDefinitionNodeTest.java | 13 +- .../commonmark/test/ListBlockParserTest.java | 7 +- .../commonmark/test/ListTightLooseTest.java | 248 ++-- .../test/java/org/commonmark/test/Nodes.java | 12 +- .../java/org/commonmark/test/ParserTest.java | 44 +- .../org/commonmark/test/PathologicalTest.java | 51 +- .../org/commonmark/test/RegressionTest.java | 21 +- .../org/commonmark/test/SourceLineTest.java | 15 +- .../commonmark/test/SourceSpanRenderer.java | 18 +- .../org/commonmark/test/SourceSpanTest.java | 24 +- .../org/commonmark/test/SourceSpansTest.java | 362 +++-- .../org/commonmark/test/SpecBenchmark.java | 15 +- .../org/commonmark/test/SpecCoreTest.java | 57 +- .../org/commonmark/test/SpecCrLfCoreTest.java | 10 +- .../org/commonmark/test/SpecialInputTest.java | 153 +- .../test/TextContentRendererTest.java | 95 +- .../test/TextContentWriterTest.java | 4 +- .../test/ThematicBreakParserTest.java | 4 +- .../org/commonmark/test/UsageExampleTest.java | 49 +- .../org/commonmark/text/CharactersTest.java | 48 +- 255 files changed, 5401 insertions(+), 4568 deletions(-) diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java index 7d5a74f30..14222121c 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkExtension.java @@ -2,7 +2,6 @@ import java.util.EnumSet; import java.util.Set; - import org.commonmark.Extension; import org.commonmark.ext.autolink.internal.AutolinkPostProcessor; import org.commonmark.parser.Parser; @@ -10,14 +9,12 @@ /** * Extension for automatically turning plain URLs and email addresses into links. - *

    - * Create it with {@link #create()} and then configure it on the builders - * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, - * {@link HtmlRenderer.Builder#extensions(Iterable)}). - *

    - *

    - * The parsed links are turned into normal {@link org.commonmark.node.Link} nodes. - *

    + * + *

    Create it with {@link #create()} and then configure it on the builders ({@link + * org.commonmark.parser.Parser.Builder#extensions(Iterable)}, {@link + * HtmlRenderer.Builder#extensions(Iterable)}). + * + *

    The parsed links are turned into normal {@link org.commonmark.node.Link} nodes. */ public class AutolinkExtension implements Parser.ParserExtension { @@ -51,8 +48,8 @@ public static class Builder { private Set linkTypes = EnumSet.allOf(AutolinkType.class); /** - * @param linkTypes the link types that should be converted. By default, - * all {@link AutolinkType}s are converted. + * @param linkTypes the link types that should be converted. By default, all {@link + * AutolinkType}s are converted. * @return {@code this} */ public Builder linkTypes(AutolinkType... linkTypes) { @@ -64,8 +61,8 @@ public Builder linkTypes(AutolinkType... linkTypes) { } /** - * @param linkTypes the link types that should be converted. By default, - * all {@link AutolinkType}s are converted. + * @param linkTypes the link types that should be converted. By default, all {@link + * AutolinkType}s are converted. * @return {@code this} */ public Builder linkTypes(Set linkTypes) { diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkType.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkType.java index 2c8c6574f..24249ef88 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkType.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkType.java @@ -1,19 +1,11 @@ package org.commonmark.ext.autolink; -/** - * The types of strings that can be automatically turned into links. - */ +/** The types of strings that can be automatically turned into links. */ public enum AutolinkType { - /** - * URL such as {@code http://example.com} - */ + /** URL such as {@code http://example.com} */ URL, - /** - * Email address such as {@code foo@example.com} - */ + /** Email address such as {@code foo@example.com} */ EMAIL, - /** - * URL such as {@code www.example.com} - */ + /** URL such as {@code www.example.com} */ WWW } diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java index a381c2f19..124870fd2 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java @@ -1,5 +1,6 @@ package org.commonmark.ext.autolink.internal; +import java.util.*; import org.commonmark.ext.autolink.AutolinkType; import org.commonmark.node.*; import org.commonmark.parser.PostProcessor; @@ -8,8 +9,6 @@ import org.nibor.autolink.LinkType; import org.nibor.autolink.Span; -import java.util.*; - public class AutolinkPostProcessor implements PostProcessor { private final LinkExtractor linkExtractor; @@ -38,9 +37,7 @@ public AutolinkPostProcessor(Set linkTypes) { } } - this.linkExtractor = LinkExtractor.builder() - .linkTypes(types) - .build(); + this.linkExtractor = LinkExtractor.builder().linkTypes(types).build(); } @Override diff --git a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java index 82c3899fc..fb79dfd60 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -1,5 +1,9 @@ package org.commonmark.ext.autolink; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.node.*; import org.commonmark.parser.IncludeSourceSpans; @@ -8,64 +12,69 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - public class AutolinkTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(AutolinkExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); - - private static final Set NO_WWW_EXTENSIONS = Set.of(AutolinkExtension.builder() - .linkTypes(AutolinkType.URL, AutolinkType.EMAIL) - .build()); - private static final Parser NO_WWW_PARSER = Parser.builder().extensions(NO_WWW_EXTENSIONS).build(); - private static final HtmlRenderer NO_WWW_RENDERER = HtmlRenderer.builder().extensions(NO_WWW_EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).build(); + + private static final Set NO_WWW_EXTENSIONS = + Set.of( + AutolinkExtension.builder() + .linkTypes(AutolinkType.URL, AutolinkType.EMAIL) + .build()); + private static final Parser NO_WWW_PARSER = + Parser.builder().extensions(NO_WWW_EXTENSIONS).build(); + private static final HtmlRenderer NO_WWW_RENDERER = + HtmlRenderer.builder().extensions(NO_WWW_EXTENSIONS).build(); @Test public void oneTextNode() { - assertRendering("foo http://one.org/ bar http://two.org/", + assertRendering( + "foo http://one.org/ bar http://two.org/", "

    foo http://one.org/ bar http://two.org/

    \n"); } @Test public void textNodeAndOthers() { - assertRendering("foo http://one.org/ bar `code` baz http://two.org/", + assertRendering( + "foo http://one.org/ bar `code` baz http://two.org/", "

    foo http://one.org/ bar code baz http://two.org/

    \n"); } @Test public void tricky() { - assertRendering("http://example.com/one. Example 2 (see http://example.com/two). Example 3: http://example.com/foo_(bar)", - "

    http://example.com/one. " + - "Example 2 (see http://example.com/two). " + - "Example 3: http://example.com/foo_(bar)

    \n"); + assertRendering( + "http://example.com/one. Example 2 (see http://example.com/two). Example 3: http://example.com/foo_(bar)", + "

    http://example.com/one. " + + "Example 2 (see http://example.com/two). " + + "Example 3: http://example.com/foo_(bar)

    \n"); } @Test public void emailUsesMailto() { - assertRendering("foo@example.com", + assertRendering( + "foo@example.com", "

    foo@example.com

    \n"); } @Test public void emailWithTldNotLinked() { - assertRendering("foo@com", - "

    foo@com

    \n"); + assertRendering("foo@com", "

    foo@com

    \n"); } @Test public void dontLinkTextWithinLinks() { - assertRendering("", + assertRendering( + "", "

    http://example.com

    \n"); } @Test public void wwwLinks() { - assertRendering("www.example.com", + assertRendering( + "www.example.com", "

    www.example.com

    \n"); } @@ -77,14 +86,17 @@ public void noWwwLinks() { @Test public void sourceSpans() { - Parser parser = Parser.builder() - .extensions(EXTENSIONS) - .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) - .build(); - Node document = parser.parse("abc\n" + - "http://example.com/one\n" + - "def http://example.com/two\n" + - "ghi http://example.com/three jkl"); + Parser parser = + Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); + Node document = + parser.parse( + "abc\n" + + "http://example.com/one\n" + + "def http://example.com/two\n" + + "ghi http://example.com/three jkl"); Paragraph paragraph = (Paragraph) document.getFirstChild(); Text abc = (Text) paragraph.getFirstChild(); diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteDefinition.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteDefinition.java index 4a560dc9e..c89bca9f8 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteDefinition.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteDefinition.java @@ -4,13 +4,16 @@ /** * A footnote definition, e.g.: + * *
    
      * [^foo]: This is the footnote text
      * 
    - * The {@link #getLabel() label} is the text in brackets after {@code ^}, so {@code foo} in the example. The contents - * of the footnote are child nodes of the definition, a {@link org.commonmark.node.Paragraph} in the example. - *

    - * Footnote definitions are parsed even if there's no corresponding {@link FootnoteReference}. + * + * The {@link #getLabel() label} is the text in brackets after {@code ^}, so {@code foo} in the + * example. The contents of the footnote are child nodes of the definition, a {@link + * org.commonmark.node.Paragraph} in the example. + * + *

    Footnote definitions are parsed even if there's no corresponding {@link FootnoteReference}. */ public class FootnoteDefinition extends CustomBlock { @@ -24,4 +27,3 @@ public String getLabel() { return label; } } - diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteReference.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteReference.java index 61dcf8626..c0382fff1 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteReference.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnoteReference.java @@ -4,9 +4,10 @@ /** * A footnote reference, e.g. [^foo] in Some text with a footnote[^foo] - *

    - * The {@link #getLabel() label} is the text within brackets after {@code ^}, so {@code foo} in the example. It needs to - * match the label of a corresponding {@link FootnoteDefinition} for the footnote to be parsed. + * + *

    The {@link #getLabel() label} is the text within brackets after {@code ^}, so {@code foo} in + * the example. It needs to match the label of a corresponding {@link FootnoteDefinition} for the + * footnote to be parsed. */ public class FootnoteReference extends CustomNode { private String label; diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java index dd532fa34..4517d14d6 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/FootnotesExtension.java @@ -1,5 +1,6 @@ package org.commonmark.ext.footnotes; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.ext.footnotes.internal.*; import org.commonmark.parser.Parser; @@ -9,32 +10,37 @@ import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; import org.commonmark.renderer.markdown.MarkdownRenderer; -import java.util.Set; - /** * Extension for footnotes with syntax like GitHub Flavored Markdown: + * *

    
      * Some text with a footnote[^1].
      *
      * [^1]: The text of the footnote.
      * 
    + * * The [^1] is a {@link FootnoteReference}, with "1" being the label. - *

    - * The line with [^1]: ... is a {@link FootnoteDefinition}, with the contents as child nodes (can be a - * paragraph like in the example, or other blocks like lists). - *

    - * All the footnotes (definitions) will be rendered in a list at the end of a document, no matter where they appear in - * the source. The footnotes will be numbered starting from 1, then 2, etc, depending on the order in which they appear - * in the text (and not dependent on the label). The footnote reference is a link to the footnote, and from the footnote - * there is a link back to the reference (or multiple). - *

    - * There is also optional support for inline footnotes, use {@link #builder()} and then set {@link Builder#inlineFootnotes}. * - * @see GitHub docs for footnotes + *

    The line with [^1]: ... is a {@link FootnoteDefinition}, with the contents as + * child nodes (can be a paragraph like in the example, or other blocks like lists). + * + *

    All the footnotes (definitions) will be rendered in a list at the end of a document, no matter + * where they appear in the source. The footnotes will be numbered starting from 1, then 2, etc, + * depending on the order in which they appear in the text (and not dependent on the label). The + * footnote reference is a link to the footnote, and from the footnote there is a link back to the + * reference (or multiple). + * + *

    There is also optional support for inline footnotes, use {@link #builder()} and then set + * {@link Builder#inlineFootnotes}. + * + * @see GitHub + * docs for footnotes */ -public class FootnotesExtension implements Parser.ParserExtension, - HtmlRenderer.HtmlRendererExtension, - MarkdownRenderer.MarkdownRendererExtension { +public class FootnotesExtension + implements Parser.ParserExtension, + HtmlRenderer.HtmlRendererExtension, + MarkdownRenderer.MarkdownRendererExtension { private final boolean inlineFootnotes; @@ -42,9 +48,7 @@ private FootnotesExtension(boolean inlineFootnotes) { this.inlineFootnotes = inlineFootnotes; } - /** - * The extension with the default configuration (no support for inline footnotes). - */ + /** The extension with the default configuration (no support for inline footnotes). */ public static Extension create() { return builder().build(); } @@ -70,17 +74,18 @@ public void extend(HtmlRenderer.Builder rendererBuilder) { @Override public void extend(MarkdownRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { - @Override - public NodeRenderer create(MarkdownNodeRendererContext context) { - return new FootnoteMarkdownNodeRenderer(context); - } + rendererBuilder.nodeRendererFactory( + new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new FootnoteMarkdownNodeRenderer(context); + } - @Override - public Set getSpecialCharacters() { - return Set.of(); - } - }); + @Override + public Set getSpecialCharacters() { + return Set.of(); + } + }); } public static class Builder { @@ -89,6 +94,7 @@ public static class Builder { /** * Enable support for inline footnotes without definitions, e.g.: + * *

              * Some text^[this is an inline footnote]
              * 
    diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/InlineFootnote.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/InlineFootnote.java index 665d01936..cb16c45f6 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/InlineFootnote.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/InlineFootnote.java @@ -2,5 +2,4 @@ import org.commonmark.node.CustomNode; -public class InlineFootnote extends CustomNode { -} +public class InlineFootnote extends CustomNode {} diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java index 110bdef20..db11b906a 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteBlockParser.java @@ -1,16 +1,13 @@ package org.commonmark.ext.footnotes.internal; +import java.util.List; import org.commonmark.ext.footnotes.FootnoteDefinition; import org.commonmark.node.Block; import org.commonmark.node.DefinitionMap; import org.commonmark.parser.block.*; import org.commonmark.text.Characters; -import java.util.List; - -/** - * Parser for a single {@link FootnoteDefinition} block. - */ +/** Parser for a single {@link FootnoteDefinition} block. */ public class FootnoteBlockParser extends AbstractBlockParser { private final FootnoteDefinition block; @@ -37,16 +34,18 @@ public boolean canContain(Block childBlock) { @Override public BlockContinue tryContinue(ParserState parserState) { if (parserState.getIndent() >= 4) { - // It looks like content needs to be indented by 4 so that it's part of a footnote (instead of starting a new block). + // It looks like content needs to be indented by 4 so that it's part of a footnote + // (instead of starting a new block). return BlockContinue.atColumn(4); } else if (parserState.isBlank()) { - // A blank line doesn't finish a footnote yet. If there's another line with indent >= 4 after it, - // that should result in another paragraph in this footnote definition. + // A blank line doesn't finish a footnote yet. If there's another line with indent >= 4 + // after it, that should result in another paragraph in this footnote definition. return BlockContinue.atIndex(parserState.getIndex()); } else { - // We're not continuing to give other block parsers a chance to interrupt this definition. - // But if no other block parser applied (including another FootnotesBlockParser), we will - // accept the line via lazy continuation (same as a block quote). + // We're not continuing to give other block parsers a chance to interrupt this + // definition. But if no other block parser applied (including another + // FootnotesBlockParser), we will accept the line via lazy continuation (same as a block + // quote). return BlockContinue.none(); } } @@ -82,11 +81,16 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar var c = content.charAt(index); switch (c) { case ']': - if (index > labelStart && index + 1 < content.length() && content.charAt(index + 1) == ':') { + if (index > labelStart + && index + 1 < content.length() + && content.charAt(index + 1) == ':') { var label = content.subSequence(labelStart, index).toString(); - // After the colon, any number of spaces is skipped (not part of the content) - var afterSpaces = Characters.skipSpaceTab(content, index + 2, content.length()); - return BlockStart.of(new FootnoteBlockParser(label)).atIndex(afterSpaces); + // After the colon, any number of spaces is skipped (not part of the + // content) + var afterSpaces = + Characters.skipSpaceTab(content, index + 2, content.length()); + return BlockStart.of(new FootnoteBlockParser(label)) + .atIndex(afterSpaces); } else { return BlockStart.none(); } diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java index 70eb048a3..db88a8269 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteHtmlNodeRenderer.java @@ -1,5 +1,7 @@ package org.commonmark.ext.footnotes.internal; +import java.util.*; +import java.util.function.Consumer; import org.commonmark.ext.footnotes.FootnoteDefinition; import org.commonmark.ext.footnotes.FootnoteReference; import org.commonmark.ext.footnotes.InlineFootnote; @@ -8,64 +10,68 @@ import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; -import java.util.*; -import java.util.function.Consumer; - /** * HTML rendering for footnotes. - *

    - * Aims to match the rendering of cmark-gfm (which is slightly different from GitHub's when it comes to class - * attributes, not sure why). - *

    - * Some notes on how rendering works: + * + *

    Aims to match the rendering of cmark-gfm (which is slightly different from GitHub's when it + * comes to class attributes, not sure why). + * + *

    Some notes on how rendering works: + * *

      - *
    • Footnotes are numbered according to the order of references, starting at 1
    • - *
    • Definitions are rendered at the end of the document, regardless of where the definition was in the source
    • - *
    • Definitions are ordered by number
    • - *
    • Definitions have links back to their references (one or more)
    • + *
    • Footnotes are numbered according to the order of references, starting at 1 + *
    • Definitions are rendered at the end of the document, regardless of where the definition was + * in the source + *
    • Definitions are ordered by number + *
    • Definitions have links back to their references (one or more) *
    * *

    Nested footnotes

    - * Text in footnote definitions can reference other footnotes, even ones that aren't referenced in the main text. - * This makes them tricky because it's not enough to just go through the main text for references. - * And before we can render a definition, we need to know all references (because we add links back to references). - *

    - * In other words, footnotes form a directed graph. Footnotes can reference each other so cycles are possible too. - *

    - * One way to implement it, which is what cmark-gfm does, is to go through the whole document (including definitions) - * and find all references in order. That guarantees that all definitions are found, but it has strange results for - * ordering or when the reference is in an unreferenced definition, see tests. In graph terms, it renders all - * definitions that have an incoming edge, no matter whether they are connected to the main text or not. - *

    - * The way we implement it: + * + * Text in footnote definitions can reference other footnotes, even ones that aren't referenced in + * the main text. This makes them tricky because it's not enough to just go through the main text + * for references. And before we can render a definition, we need to know all references (because we + * add links back to references). + * + *

    In other words, footnotes form a directed graph. Footnotes can reference each other so cycles + * are possible too. + * + *

    One way to implement it, which is what cmark-gfm does, is to go through the whole document + * (including definitions) and find all references in order. That guarantees that all definitions + * are found, but it has strange results for ordering or when the reference is in an unreferenced + * definition, see tests. In graph terms, it renders all definitions that have an incoming edge, no + * matter whether they are connected to the main text or not. + * + *

    The way we implement it: + * *

      - *
    1. Start with the references in the main text; we can render them as we go
    2. - *
    3. After the main text is rendered, we have the referenced definitions, but there might be more from definition text
    4. - *
    5. To find the remaining definitions, we visit the definitions from before to look at references
    6. - *
    7. Repeat (breadth-first search) until we've found all definitions (note that we can't render before that's done because of backrefs)
    8. - *
    9. Now render the definitions (and any references inside)
    10. + *
    11. Start with the references in the main text; we can render them as we go + *
    12. After the main text is rendered, we have the referenced definitions, but there might be + * more from definition text + *
    13. To find the remaining definitions, we visit the definitions from before to look at + * references + *
    14. Repeat (breadth-first search) until we've found all definitions (note that we can't render + * before that's done because of backrefs) + *
    15. Now render the definitions (and any references inside) *
    - * This means we only render definitions whose references are actually rendered, and in a meaningful order (all main - * text footnotes first, then any nested ones). + * + * This means we only render definitions whose references are actually rendered, and in a meaningful + * order (all main text footnotes first, then any nested ones). */ public class FootnoteHtmlNodeRenderer implements NodeRenderer { private final HtmlWriter html; private final HtmlNodeRendererContext context; - /** - * All definitions (even potentially unused ones), for looking up references - */ + /** All definitions (even potentially unused ones), for looking up references */ private DefinitionMap definitionMap; - /** - * Definitions that were referenced, in order in which they should be rendered. - */ + /** Definitions that were referenced, in order in which they should be rendered. */ private final Map referencedDefinitions = new LinkedHashMap<>(); /** - * Information about references that should be rendered as footnotes. This doesn't contain all references, just the - * ones from inside definitions. + * Information about references that should be rendered as footnotes. This doesn't contain all + * references, just the ones from inside definitions. */ private final Map references = new HashMap<>(); @@ -81,7 +87,8 @@ public Set> getNodeTypes() { @Override public void beforeRoot(Node rootNode) { - // Collect all definitions first, so we can look them up when encountering a reference later. + // Collect all definitions first, so we can look them up when encountering a reference + // later. var visitor = new DefinitionVisitor(); rootNode.accept(visitor); definitionMap = visitor.definitions; @@ -90,11 +97,12 @@ public void beforeRoot(Node rootNode) { @Override public void render(Node node) { if (node instanceof FootnoteReference) { - // This is called for all references, even ones inside definitions that we render at the end. - // Inside definitions, we have registered the reference already. + // This is called for all references, even ones inside definitions that we render at the + // end. Inside definitions, we have registered the reference already. var ref = (FootnoteReference) node; // Use containsKey because if value is null, we don't need to try registering again. - var info = references.containsKey(ref) ? references.get(ref) : tryRegisterReference(ref); + var info = + references.containsKey(ref) ? references.get(ref) : tryRegisterReference(ref); if (info != null) { renderReference(ref, info); } else { @@ -126,26 +134,30 @@ public void afterRoot(Node rootNode) { html.tag("ol"); html.line(); - // Check whether there are any footnotes inside the definitions that we're about to render. For those, we might - // need to render more definitions. So do a breadth-first search to find all relevant definitions. + // Check whether there are any footnotes inside the definitions that we're about to render. + // For those, we might need to render more definitions. So do a breadth-first search to find + // all relevant definitions. var check = new LinkedList<>(referencedDefinitions.keySet()); while (!check.isEmpty()) { var def = check.removeFirst(); - def.accept(new ShallowReferenceVisitor(def, node -> { - if (node instanceof FootnoteReference) { - var ref = (FootnoteReference) node; - var d = definitionMap.get(ref.getLabel()); - if (d != null) { - if (!referencedDefinitions.containsKey(d)) { - check.addLast(d); - } - references.put(ref, registerReference(d, d.getLabel())); - } - } else if (node instanceof InlineFootnote) { - check.addLast(node); - references.put(node, registerReference(node, null)); - } - })); + def.accept( + new ShallowReferenceVisitor( + def, + node -> { + if (node instanceof FootnoteReference) { + var ref = (FootnoteReference) node; + var d = definitionMap.get(ref.getLabel()); + if (d != null) { + if (!referencedDefinitions.containsKey(d)) { + check.addLast(d); + } + references.put(ref, registerReference(d, d.getLabel())); + } + } else if (node instanceof InlineFootnote) { + check.addLast(node); + references.put(node, registerReference(node, null)); + } + })); } for (var entry : referencedDefinitions.entrySet()) { @@ -169,15 +181,19 @@ private ReferenceInfo tryRegisterReference(FootnoteReference ref) { private ReferenceInfo registerReference(Node node, String label) { // The first referenced definition gets number 1, second one 2, etc. - var referencedDef = referencedDefinitions.computeIfAbsent(node, k -> { - var num = referencedDefinitions.size() + 1; - var key = definitionKey(label, num); - return new ReferencedDefinition(num, key); - }); + var referencedDef = + referencedDefinitions.computeIfAbsent( + node, + k -> { + var num = referencedDefinitions.size() + 1; + var key = definitionKey(label, num); + return new ReferencedDefinition(num, key); + }); var definitionNumber = referencedDef.definitionNumber; - // The reference number for that particular definition. E.g. if there's two references for the same definition, - // the first one is 1, the second one 2, etc. This is needed to give each reference a unique ID so that each - // reference can get its own backlink from the definition. + // The reference number for that particular definition. E.g. if there's two references for + // the same definition, the first one is 1, the second one 2, etc. This is needed to give + // each reference a unique ID so that each reference can get its own backlink from the + // definition. var refNumber = referencedDef.references.size() + 1; var definitionKey = referencedDef.definitionKey; var id = referenceId(definitionKey, refNumber); @@ -212,8 +228,9 @@ private void renderDefinition(Node def, ReferencedDefinition referencedDefinitio var node = def.getFirstChild(); while (node != lastParagraph) { if (node instanceof Paragraph) { - // Because we're manually rendering the

    for the last paragraph, do the same for all other - // paragraphs for consistency (Paragraph rendering might be overwritten by a custom renderer). + // Because we're manually rendering the

    for the last paragraph, do the same + // for all other paragraphs for consistency (Paragraph rendering might be + // overwritten by a custom renderer). html.tag("p", context.extendAttributes(node, "p", Map.of())); renderChildren(node); html.tag("/p"); @@ -252,7 +269,9 @@ private void renderBackrefs(Node def, ReferencedDefinition referencedDefinition) for (int i = 0; i < refs.size(); i++) { var ref = refs.get(i); var refNumber = i + 1; - var idx = referencedDefinition.definitionNumber + (refNumber > 1 ? ("-" + refNumber) : ""); + var idx = + referencedDefinition.definitionNumber + + (refNumber > 1 ? ("-" + refNumber) : ""); var attrs = new LinkedHashMap(); attrs.put("href", "#" + ref); @@ -262,7 +281,9 @@ private void renderBackrefs(Node def, ReferencedDefinition referencedDefinition) attrs.put("aria-label", "Back to reference " + idx); html.tag("a", context.extendAttributes(def, "a", attrs)); if (refNumber > 1) { - html.tag("sup", context.extendAttributes(def, "sup", Map.of("class", "footnote-ref"))); + html.tag( + "sup", + context.extendAttributes(def, "sup", Map.of("class", "footnote-ref"))); html.raw(String.valueOf(refNumber)); html.tag("/sup"); } @@ -280,8 +301,9 @@ private String referenceId(String definitionKey, int number) { } private String definitionKey(String label, int number) { - // Named definitions use the pattern "fn-{name}" and inline definitions use "fn{number}" so as not to conflict. - // "fn{number}" is also what pandoc uses (for all types), starting with number 1. + // Named definitions use the pattern "fn-{name}" and inline definitions use "fn{number}" so + // as not to conflict. "fn{number}" is also what pandoc uses (for all types), starting with + // number 1. if (label != null) { return "-" + label; } else { @@ -304,7 +326,8 @@ private void renderChildren(Node parent) { private static class DefinitionVisitor extends AbstractVisitor { - private final DefinitionMap definitions = new DefinitionMap<>(FootnoteDefinition.class); + private final DefinitionMap definitions = + new DefinitionMap<>(FootnoteDefinition.class); @Override public void visit(CustomBlock customBlock) { @@ -318,8 +341,8 @@ public void visit(CustomBlock customBlock) { } /** - * Visit footnote references/inline footnotes inside the parent (but not the parent itself). We want a shallow visit - * because the caller wants to control when to descend. + * Visit footnote references/inline footnotes inside the parent (but not the parent itself). We + * want a shallow visit because the caller wants to control when to descend. */ private static class ShallowReferenceVisitor extends AbstractVisitor { private final Node parent; @@ -349,17 +372,16 @@ public void visit(CustomNode customNode) { } private static class ReferencedDefinition { - /** - * The definition number, starting from 1, and in order in which they're referenced. - */ + /** The definition number, starting from 1, and in order in which they're referenced. */ final int definitionNumber; + /** - * The unique key of the definition. Together with a static prefix it forms the ID used in the HTML. + * The unique key of the definition. Together with a static prefix it forms the ID used in + * the HTML. */ final String definitionKey; - /** - * The IDs of references for this definition, for backrefs. - */ + + /** The IDs of references for this definition, for backrefs. */ final List references = new ArrayList<>(); ReferencedDefinition(int definitionNumber, String definitionKey) { @@ -370,16 +392,15 @@ private static class ReferencedDefinition { private static class ReferenceInfo { /** - * The ID of the reference; in the corresponding definition, a link back to this reference will be rendered. + * The ID of the reference; in the corresponding definition, a link back to this reference + * will be rendered. */ private final String id; - /** - * The ID of the definition, for linking to the definition. - */ + + /** The ID of the definition, for linking to the definition. */ private final String definitionId; - /** - * The definition number, rendered in superscript. - */ + + /** The definition number, rendered in superscript. */ private final int definitionNumber; private ReferenceInfo(String id, String definitionId, int definitionNumber) { diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java index 07b008576..66a889855 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteLinkProcessor.java @@ -11,17 +11,18 @@ import org.commonmark.parser.beta.Scanner; /** - * For turning e.g. [^foo] into a {@link FootnoteReference}, - * and ^[foo] into an {@link InlineFootnote}. + * For turning e.g. [^foo] into a {@link FootnoteReference}, and ^[foo] + * into an {@link InlineFootnote}. */ public class FootnoteLinkProcessor implements LinkProcessor { @Override public LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContext context) { if (linkInfo.marker() != null && linkInfo.marker().getLiteral().equals("^")) { - // An inline footnote like ^[footnote text]. Note that we only get the marker here if the option is enabled - // on the extension. - return LinkResult.wrapTextIn(new InlineFootnote(), linkInfo.afterTextBracket()).includeMarker(); + // An inline footnote like ^[footnote text]. Note that we only get the marker here if + // the option is enabled on the extension. + return LinkResult.wrapTextIn(new InlineFootnote(), linkInfo.afterTextBracket()) + .includeMarker(); } if (linkInfo.destination() != null) { @@ -35,21 +36,25 @@ public LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContex return LinkResult.none(); } - if (linkInfo.label() != null && context.getDefinition(LinkReferenceDefinition.class, linkInfo.label()) != null) { - // If there's a label after the text and the label has a definition -> it's a link, and it should take - // preference, e.g. in `[^foo][bar]` if `[bar]` has a definition, `[^foo]` won't be a footnote reference. + if (linkInfo.label() != null + && context.getDefinition(LinkReferenceDefinition.class, linkInfo.label()) != null) { + // If there's a label after the text and the label has a definition -> it's a link, and + // it should take preference, e.g. in `[^foo][bar]` if `[bar]` has a definition, + // `[^foo]` won't be a footnote reference. return LinkResult.none(); } var label = text.substring(1); - // Check if we have a definition, otherwise ignore (same behavior as for link reference definitions). - // Note that the definition parser already checked the syntax of the label, we don't need to check again. + // Check if we have a definition, otherwise ignore (same behavior as for link reference + // definitions). Note that the definition parser already checked the syntax of the label, we + // don't need to check again. var def = context.getDefinition(FootnoteDefinition.class, label); if (def == null) { return LinkResult.none(); } - // For footnotes, we only ever consume the text part of the link, not the label part (if any) + // For footnotes, we only ever consume the text part of the link, not the label part (if + // any) var position = linkInfo.afterTextBracket(); // If the marker is `![`, we don't want to include the `!`, so start from bracket return LinkResult.replaceWith(new FootnoteReference(label), position); diff --git a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java index 3dcf4fc83..0e654cf5e 100644 --- a/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java +++ b/commonmark-ext-footnotes/src/main/java/org/commonmark/ext/footnotes/internal/FootnoteMarkdownNodeRenderer.java @@ -1,5 +1,6 @@ package org.commonmark.ext.footnotes.internal; +import java.util.Set; import org.commonmark.ext.footnotes.FootnoteDefinition; import org.commonmark.ext.footnotes.FootnoteReference; import org.commonmark.ext.footnotes.InlineFootnote; @@ -8,8 +9,6 @@ import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; import org.commonmark.renderer.markdown.MarkdownWriter; -import java.util.Set; - public class FootnoteMarkdownNodeRenderer implements NodeRenderer { private final MarkdownWriter writer; diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java index bc7d4f74c..1a89693b4 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java @@ -1,5 +1,7 @@ package org.commonmark.ext.footnotes; +import java.util.List; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.node.Document; import org.commonmark.node.Paragraph; @@ -10,40 +12,40 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.Set; - public class FootnoteHtmlRendererTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(FootnotesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).build(); @Test public void testOne() { - assertRendering("Test [^foo]\n\n[^foo]: note\n", - "

    Test 1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      note

      \n" + - "
    2. \n" + - "
    \n" + - "
    \n"); + assertRendering( + "Test [^foo]\n\n[^foo]: note\n", + "

    Test 1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      note

      \n" + + "
    2. \n" + + "
    \n" + + "
    \n"); } @Test public void testLabelNormalization() { - // Labels match via their normalized form. For the href and IDs to match, rendering needs to use the - // label from the definition consistently. - assertRendering("Test [^bar]\n\n[^BAR]: note\n", - "

    Test 1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      note

      \n" + - "
    2. \n" + - "
    \n" + - "
    \n"); + // Labels match via their normalized form. For the href and IDs to match, rendering needs to + // use the label from the definition consistently. + assertRendering( + "Test [^bar]\n\n[^BAR]: note\n", + "

    Test 1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      note

      \n" + + "
    2. \n" + + "
    \n" + + "
    \n"); } @Test @@ -52,254 +54,251 @@ public void testMultipleReferences() { // - Numbering is based on the reference order, not the definition order // - The same number is used when a definition is referenced multiple times // - Multiple backrefs are rendered - assertRendering("First [^foo]\n\nThen [^bar]\n\nThen [^foo] again\n\n[^bar]: b\n[^foo]: f\n", - "

    First 1

    \n" + - "

    Then 2

    \n" + - "

    Then 1 again

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      f 2

      \n" + - "
    2. \n" + - "
    3. \n" + - "

      b

      \n" + - "
    4. \n" + - "
    \n" + - "
    \n"); + assertRendering( + "First [^foo]\n\nThen [^bar]\n\nThen [^foo] again\n\n[^bar]: b\n[^foo]: f\n", + "

    First 1

    \n" + + "

    Then 2

    \n" + + "

    Then 1 again

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      f 2

      \n" + + "
    2. \n" + + "
    3. \n" + + "

      b

      \n" + + "
    4. \n" + + "
    \n" + + "
    \n"); } @Test public void testDefinitionWithTwoParagraphs() { // With two paragraphs, the backref should be added to the second one - assertRendering("Test [^foo]\n\n[^foo]: one\n \n two\n", - "

    Test 1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      one

      \n" + - "

      two

      \n" + - "
    2. \n" + - "
    \n" + - "
    \n"); + assertRendering( + "Test [^foo]\n\n[^foo]: one\n \n two\n", + "

    Test 1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      one

      \n" + + "

      two

      \n" + + "
    2. \n" + + "
    \n" + + "
    \n"); } @Test public void testDefinitionWithList() { - assertRendering("Test [^foo]\n\n[^foo]:\n - one\n - two\n", - "

    Test 1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "
        \n" + - "
      • one
      • \n" + - "
      • two
      • \n" + - "
      \n" + - "
    2. \n" + - "
    \n" + - "
    \n"); + assertRendering( + "Test [^foo]\n\n[^foo]:\n - one\n - two\n", + "

    Test 1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "
        \n" + + "
      • one
      • \n" + + "
      • two
      • \n" + + "
      \n" + + "
    2. \n" + + "
    \n" + + "
    \n"); } // See docs on FootnoteHtmlNodeRenderer about nested footnotes. @Test public void testNestedFootnotesSimple() { - assertRendering("[^foo1]\n" + - "\n" + - "[^foo1]: one [^foo2]\n" + - "[^foo2]: two\n", "

    1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      one 2

      \n" + - "
    2. \n" + - "
    3. \n" + - "

      two

      \n" + - "
    4. \n" + - "
    \n" + - "
    \n"); + assertRendering( + "[^foo1]\n" + "\n" + "[^foo1]: one [^foo2]\n" + "[^foo2]: two\n", + "

    1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      one 2

      \n" + + "
    2. \n" + + "
    3. \n" + + "

      two

      \n" + + "
    4. \n" + + "
    \n" + + "
    \n"); } @Test public void testNestedFootnotesOrder() { // GitHub has a strange result here, the definitions are in order: 1. bar, 2. foo. - // The reason is that the number is done based on all references in document order, including references in - // definitions. So [^bar] from the first line is first. - assertRendering("[^foo]: foo [^bar]\n" + - "\n" + - "[^foo]\n" + - "\n" + - "[^bar]: bar\n", "

    1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      foo 2

      \n" + - "
    2. \n" + - "
    3. \n" + - "

      bar

      \n" + - "
    4. \n" + - "
    \n" + - "
    \n"); + // The reason is that the number is done based on all references in document order, + // including references in definitions. So [^bar] from the first line is first. + assertRendering( + "[^foo]: foo [^bar]\n" + "\n" + "[^foo]\n" + "\n" + "[^bar]: bar\n", + "

    1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      foo 2

      \n" + + "
    2. \n" + + "
    3. \n" + + "

      bar

      \n" + + "
    4. \n" + + "
    \n" + + "
    \n"); } @Test public void testNestedFootnotesOrder2() { - assertRendering("[^1]\n" + - "\n" + - "[^4]: four\n" + - "[^3]: three [^4]\n" + - "[^2]: two [^4]\n" + - "[^1]: one [^2][^3]\n", "

    1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      one 23

      \n" + - "
    2. \n" + - "
    3. \n" + - "

      two 4

      \n" + - "
    4. \n" + - "
    5. \n" + - "

      three 4

      \n" + - "
    6. \n" + - "
    7. \n" + - "

      four 2

      \n" + - "
    8. \n" + - "
    \n" + - "
    \n"); + assertRendering( + "[^1]\n" + + "\n" + + "[^4]: four\n" + + "[^3]: three [^4]\n" + + "[^2]: two [^4]\n" + + "[^1]: one [^2][^3]\n", + "

    1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      one 23

      \n" + + "
    2. \n" + + "
    3. \n" + + "

      two 4

      \n" + + "
    4. \n" + + "
    5. \n" + + "

      three 4

      \n" + + "
    6. \n" + + "
    7. \n" + + "

      four 2

      \n" + + "
    8. \n" + + "
    \n" + + "
    \n"); } @Test public void testNestedFootnotesCycle() { // Footnotes can contain cycles, lol. - assertRendering("[^foo1]\n" + - "\n" + - "[^foo1]: one [^foo2]\n" + - "[^foo2]: two [^foo1]\n", "

    1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      one 2 2

      \n" + - "
    2. \n" + - "
    3. \n" + - "

      two 1

      \n" + - "
    4. \n" + - "
    \n" + - "
    \n"); + assertRendering( + "[^foo1]\n" + "\n" + "[^foo1]: one [^foo2]\n" + "[^foo2]: two [^foo1]\n", + "

    1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      one 2 2

      \n" + + "
    2. \n" + + "
    3. \n" + + "

      two 1

      \n" + + "
    4. \n" + + "
    \n" + + "
    \n"); } @Test public void testNestedFootnotesUnreferenced() { // This should not result in any footnotes, as baz itself isn't referenced. // But GitHub renders bar only, with a broken backref, because bar is referenced from foo. - assertRendering("[^foo]: foo[^bar]\n" + - "[^bar]: bar\n", ""); + assertRendering("[^foo]: foo[^bar]\n" + "[^bar]: bar\n", ""); // And here only 1 is rendered. - assertRendering("[^1]\n" + - "\n" + - "[^1]: one\n" + - "[^foo]: foo[^bar]\n" + - "[^bar]: bar\n", "

    1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      one

      \n" + - "
    2. \n" + - "
    \n" + - "
    \n"); + assertRendering( + "[^1]\n" + "\n" + "[^1]: one\n" + "[^foo]: foo[^bar]\n" + "[^bar]: bar\n", + "

    1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      one

      \n" + + "
    2. \n" + + "
    \n" + + "
    \n"); } @Test public void testInlineFootnotes() { - assertRenderingInline("Test ^[inline *footnote*]", - "

    Test 1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      inline footnote

      \n" + - "
    2. \n" + - "
    \n" + - "
    \n"); + assertRenderingInline( + "Test ^[inline *footnote*]", + "

    Test 1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      inline footnote

      \n" + + "
    2. \n" + + "
    \n" + + "
    \n"); } @Test public void testInlineFootnotesNested() { - assertRenderingInline("Test ^[inline ^[nested]]", - "

    Test 1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      inline 2

      \n" + - "
    2. \n" + - "
    3. \n" + - "

      nested

      \n" + - "
    4. \n" + - "
    \n" + - "
    \n"); + assertRenderingInline( + "Test ^[inline ^[nested]]", + "

    Test 1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      inline 2

      \n" + + "
    2. \n" + + "
    3. \n" + + "

      nested

      \n" + + "
    4. \n" + + "
    \n" + + "
    \n"); } @Test public void testInlineFootnoteWithReference() { // This is a bit tricky because the IDs need to be unique. - assertRenderingInline("Test ^[inline [^1]]\n" + - "\n" + - "[^1]: normal", - "

    Test 1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      inline 2

      \n" + - "
    2. \n" + - "
    3. \n" + - "

      normal

      \n" + - "
    4. \n" + - "
    \n" + - "
    \n"); + assertRenderingInline( + "Test ^[inline [^1]]\n" + "\n" + "[^1]: normal", + "

    Test 1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      inline 2

      \n" + + "
    2. \n" + + "
    3. \n" + + "

      normal

      \n" + + "
    4. \n" + + "
    \n" + + "
    \n"); } @Test public void testInlineFootnoteInsideDefinition() { - assertRenderingInline("Test [^1]\n" + - "\n" + - "[^1]: Definition ^[inline]\n", - "

    Test 1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      Definition 2

      \n" + - "
    2. \n" + - "
    3. \n" + - "

      inline

      \n" + - "
    4. \n" + - "
    \n" + - "
    \n"); + assertRenderingInline( + "Test [^1]\n" + "\n" + "[^1]: Definition ^[inline]\n", + "

    Test 1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      Definition 2

      \n" + + "
    2. \n" + + "
    3. \n" + + "

      inline

      \n" + + "
    4. \n" + + "
    \n" + + "
    \n"); } @Test public void testInlineFootnoteInsideDefinition2() { - // Tricky because of the nested inline footnote which we want to visit after foo (breadth-first). - assertRenderingInline("Test [^1]\n" + - "\n" + - "[^1]: Definition ^[inline ^[nested]] ^[foo]\n", - "

    Test 1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      Definition 2 3

      \n" + - "
    2. \n" + - "
    3. \n" + - "

      inline 4

      \n" + - "
    4. \n" + - "
    5. \n" + - "

      foo

      \n" + - "
    6. \n" + - "
    7. \n" + - "

      nested

      \n" + - "
    8. \n" + - "
    \n" + - "
    \n"); + // Tricky because of the nested inline footnote which we want to visit after foo + // (breadth-first). + assertRenderingInline( + "Test [^1]\n" + "\n" + "[^1]: Definition ^[inline ^[nested]] ^[foo]\n", + "

    Test 1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      Definition 2 3

      \n" + + "
    2. \n" + + "
    3. \n" + + "

      inline 4

      \n" + + "
    4. \n" + + "
    5. \n" + + "

      foo

      \n" + + "
    6. \n" + + "
    7. \n" + + "

      nested

      \n" + + "
    8. \n" + + "
    \n" + + "
    \n"); } - @Test public void testRenderNodesDirectly() { // Everything should work as expected when rendering from nodes directly (no parsing step). @@ -314,14 +313,15 @@ public void testRenderNodesDirectly() { doc.appendChild(p); doc.appendChild(def); - var expected = "

    Test 1

    \n" + - "
    \n" + - "
      \n" + - "
    1. \n" + - "

      note!

      \n" + - "
    2. \n" + - "
    \n" + - "
    \n"; + var expected = + "

    Test 1

    \n" + + "
    \n" + + "
      \n" + + "
    1. \n" + + "

      note!

      \n" + + "
    2. \n" + + "
    \n" + + "
    \n"; Asserts.assertRendering("", expected, RENDERER.render(doc)); } diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java index 2f1125a02..3c6febe7b 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteMarkdownRendererTest.java @@ -1,19 +1,20 @@ package org.commonmark.ext.footnotes; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; import org.commonmark.Extension; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.jupiter.api.Test; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - public class FootnoteMarkdownRendererTest { - private static final Set EXTENSIONS = Set.of(FootnotesExtension.builder().inlineFootnotes(true).build()); + private static final Set EXTENSIONS = + Set.of(FootnotesExtension.builder().inlineFootnotes(true).build()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = + MarkdownRenderer.builder().extensions(EXTENSIONS).build(); @Test public void testSimple() { @@ -22,7 +23,8 @@ public void testSimple() { @Test public void testUnreferenced() { - // Whether a reference has a corresponding definition or vice versa shouldn't matter for Markdown rendering. + // Whether a reference has a corresponding definition or vice versa shouldn't matter for + // Markdown rendering. assertRoundTrip("Test [^foo]\n\n[^foo]: one\n\n[^bar]: two\n"); } @@ -43,8 +45,8 @@ public void testMultipleLines() { @Test public void testMultipleParagraphs() { - // Note that the line between p1 and p2 could be blank too (instead of 4 spaces), but we currently don't - // preserve that information. + // Note that the line between p1 and p2 could be blank too (instead of 4 spaces), but we + // currently don't preserve that information. assertRoundTrip("Test [^1]\n\n[^1]: footnote p1\n \n footnote p2\n"); } diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java index 7763cedb4..ca76e47cd 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnotesTest.java @@ -1,17 +1,16 @@ package org.commonmark.ext.footnotes; -import org.commonmark.Extension; -import org.commonmark.node.*; -import org.commonmark.parser.IncludeSourceSpans; -import org.commonmark.parser.Parser; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; +import org.commonmark.Extension; +import org.commonmark.node.*; +import org.commonmark.parser.IncludeSourceSpans; +import org.commonmark.parser.Parser; +import org.junit.jupiter.api.Test; public class FootnotesTest { @@ -35,7 +34,8 @@ public void testDefBlockStart() { @Test public void testDefBlockStartInterrupts() { - // This is different from a link reference definition, which can only be at the start of paragraphs. + // This is different from a link reference definition, which can only be at the start of + // paragraphs. var doc = PARSER.parse("test\n[^1]: footnote\n"); var paragraph = find(doc, Paragraph.class); var def = find(doc, FootnoteDefinition.class); @@ -172,7 +172,8 @@ public void testRefWithEmphasisInside() { @Test public void testRefWithEmphasisAround() { - // Emphasis around footnote reference, the * inside needs to be removed from emphasis processing + // Emphasis around footnote reference, the * inside needs to be removed from emphasis + // processing var doc = PARSER.parse("Test *abc [^foo*] def*\n\n[^foo*]: def\n"); var ref = find(doc, FootnoteReference.class); assertThat(ref.getLabel()).isEqualTo("foo*"); @@ -193,8 +194,9 @@ public void testRefAfterBang() { @Test public void testRefAsLabelOnly() { - // [^bar] is a footnote but [foo] is just text, because full reference links (text `foo`, label `^bar`) don't - // resolve as footnotes. If `[foo][^bar]` fails to parse as a bracket, `[^bar]` by itself needs to be tried. + // [^bar] is a footnote but [foo] is just text, because full reference links (text `foo`, + // label `^bar`) don't resolve as footnotes. If `[foo][^bar]` fails to parse as a bracket, + // `[^bar]` by itself needs to be tried. var doc = PARSER.parse("Test [foo][^bar]\n\n[^bar]: footnote\n"); var ref = find(doc, FootnoteReference.class); assertThat(ref.getLabel()).isEqualTo("bar"); @@ -204,7 +206,8 @@ public void testRefAsLabelOnly() { @Test public void testRefWithEmptyLabel() { - // [^bar] is a footnote but [] is just text, because collapsed reference links don't resolve as footnotes + // [^bar] is a footnote but [] is just text, because collapsed reference links don't resolve + // as footnotes var doc = PARSER.parse("Test [^bar][]\n\n[^bar]: footnote\n"); var ref = find(doc, FootnoteReference.class); assertThat(ref.getLabel()).isEqualTo("bar"); @@ -237,8 +240,8 @@ public void testPreferInlineLink() { @Test public void testPreferReferenceLink() { - // This is tricky because `[^*foo*][foo]` is a valid link already. If `[foo]` was not defined, the first bracket - // would be a footnote. + // This is tricky because `[^*foo*][foo]` is a valid link already. If `[foo]` was not + // defined, the first bracket would be a footnote. var doc = PARSER.parse("Test [^*foo*][foo]\n\n[^*foo*]: /url\n\n[foo]: /url"); assertNone(doc, FootnoteReference.class); } @@ -256,10 +259,13 @@ public void testReferenceLinkWithoutDefinition() { @Test public void testFootnoteInLink() { - // Expected to behave the same way as a link within a link, see https://spec.commonmark.org/0.31.2/#example-518 - // i.e. the first (inner) link is parsed, which means the outer one becomes plain text, as nesting links is not - // allowed. - var doc = PARSER.parse("[link with footnote ref [^1]](https://example.com)\n\n[^1]: footnote\n"); + // Expected to behave the same way as a link within a link, see + // https://spec.commonmark.org/0.31.2/#example-518 + // i.e. the first (inner) link is parsed, which means the outer one becomes plain text, as + // nesting links is not allowed. + var doc = + PARSER.parse( + "[link with footnote ref [^1]](https://example.com)\n\n[^1]: footnote\n"); var ref = find(doc, FootnoteReference.class); assertThat(ref.getLabel()).isEqualTo("1"); var paragraph = doc.getFirstChild(); @@ -269,7 +275,9 @@ public void testFootnoteInLink() { @Test public void testFootnoteWithMarkerInLink() { - var doc = PARSER.parse("[link with footnote ref ![^1]](https://example.com)\n\n[^1]: footnote\n"); + var doc = + PARSER.parse( + "[link with footnote ref ![^1]](https://example.com)\n\n[^1]: footnote\n"); var ref = find(doc, FootnoteReference.class); assertThat(ref.getLabel()).isEqualTo("1"); var paragraph = doc.getFirstChild(); @@ -325,7 +333,11 @@ public void testInlineFootnote() { @Test public void testSourcePositions() { - var parser = Parser.builder().extensions(EXTENSIONS).includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES).build(); + var parser = + Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); var doc = parser.parse("Test [^foo]\n\n[^foo]: /url\n"); var ref = find(doc, FootnoteReference.class); @@ -336,11 +348,15 @@ public void testSourcePositions() { } private static void assertNone(Node parent, Class nodeClass) { - assertThat(tryFind(parent, nodeClass)).as(() -> "Node " + parent + " containing " + nodeClass).isNull(); + assertThat(tryFind(parent, nodeClass)) + .as(() -> "Node " + parent + " containing " + nodeClass) + .isNull(); } private static T find(Node parent, Class nodeClass) { - return Objects.requireNonNull(tryFind(parent, nodeClass), "Could not find a " + nodeClass.getSimpleName() + " node in " + parent); + return Objects.requireNonNull( + tryFind(parent, nodeClass), + "Could not find a " + nodeClass.getSimpleName() + " node in " + parent); } private static T tryFind(Node parent, Class nodeClass) { diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java index 71c35d24d..5725d53cd 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java @@ -21,31 +21,32 @@ public String getType() { /** * @return Whether this alert has any body content (not including the title). - *

    + *

    - Examples where this would be {@code true}: + *

    {@code
    +     * > [!NOTE]
    +     * > Body text
          *
    -     * - Examples where this would be {@code true}:
    -     *   
    {@code
    -     *   > [!NOTE]
    -     *   > Body text
    -     *   }
    - *
    {@code
    -     *   > [!NOTE] Custom title
    -     *   > Body text
    -     *   }
    + * }
    + *
    {@code
    +     * > [!NOTE] Custom title
    +     * > Body text
          *
    -     * - Examples where this would be {@code false}:
    +     * }
    + * - Examples where this would be {@code false}: + *
    {@code
    +     * > [!NOTE]
          *
    -     *   
    {@code
    -     *   > [!NOTE]
    -     *   }
    - *
    {@code
    -     *   > [!NOTE]
    -     *   >
    -     *   >
    -     *   }
    - *
    {@code
    -     *   > [!NOTE] Custom title
    -     *   }
    + * }
    + *
    {@code
    +     * > [!NOTE]
    +     * >
    +     * >
    +     *
    +     * }
    + *
    {@code
    +     * > [!NOTE] Custom title
    +     *
    +     * }
    */ public boolean hasBody() { var first = this.getFirstChild(); diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertTitle.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertTitle.java index c085af746..fc537759c 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertTitle.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertTitle.java @@ -5,20 +5,18 @@ /** * Inline content container for the optional custom title of an {@link Alert}. * - *

    - * When present, an {@code AlertTitle} is always the first child of an {@link Alert}. - * Its own children are the parsed inline nodes of the title (i.e., the text after - * the {@code [!TYPE]} marker on the same line). For example, in + *

    When present, an {@code AlertTitle} is always the first child of an {@link Alert}. Its own + * children are the parsed inline nodes of the title (i.e., the text after the {@code [!TYPE]} + * marker on the same line). For example, in * *

    {@code
      * > [!NOTE] Custom _title_
      * > Body text
      * }
    * - * the {@code AlertTitle} contains a {@code Text} node ({@code "Custom "}) followed - * by an {@code Emphasis} node wrapping {@code "title"}. + * the {@code AlertTitle} contains a {@code Text} node ({@code "Custom "}) followed by an {@code + * Emphasis} node wrapping {@code "title"}. * * @see AlertsExtension.Builder#allowCustomTitles(boolean) */ -public class AlertTitle extends CustomNode { -} +public class AlertTitle extends CustomNode {} diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index ed81a2c3f..3035c9952 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -1,5 +1,11 @@ package org.commonmark.ext.gfm.alerts; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.ext.gfm.alerts.internal.AlertBlockParser; import org.commonmark.ext.gfm.alerts.internal.AlertHtmlNodeRenderer; @@ -13,61 +19,60 @@ import org.commonmark.renderer.markdown.MarkdownRenderer; import org.commonmark.renderer.text.TextContentRenderer; -import java.util.HashMap; -import java.util.Locale; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - /** * Extension for GFM alerts using {@code [!TYPE]} syntax (GitHub Flavored Markdown). - *

    - * Create with {@link #create()} or {@link #builder()} and configure on builders - * ({@link Parser.Builder#extensions(Iterable)}, {@link HtmlRenderer.Builder#extensions(Iterable)}). - * Parsed alerts become {@link Alert} blocks. If custom alert titles are allowed - * via {@link Builder#allowCustomTitles(boolean)}, the inline formatting of those - * titles will be parsed into {@link AlertTitle} nodes. * - * The {@link #create() default configuration} of this extension will match GFM - * exactly, with the following exceptions: + *

    Create with {@link #create()} or {@link #builder()} and configure on builders ({@link + * Parser.Builder#extensions(Iterable)}, {@link HtmlRenderer.Builder#extensions(Iterable)}). Parsed + * alerts become {@link Alert} blocks. If custom alert titles are allowed via {@link + * Builder#allowCustomTitles(boolean)}, the inline formatting of those titles will be parsed into + * {@link AlertTitle} nodes. + * + *

    The {@link #create() default configuration} of this extension will match GFM exactly, with the + * following exceptions: * - * - Alert markers take precedence over shortcut reference links. + *

    - Alert markers take precedence over shortcut reference links. * - Alerts with no content are allowed. Example: * - *

    {@code
    - *   
    - *   > [!NOTE]
    + * 
    {@code
    + * 
    + * > [!NOTE]
    + *
    + * 
    + * > [!NOTE] Custom title
    + *
    + * }
    * - * - * > [!NOTE] Custom title - * }
    * - Lazy continuation is not allowed between the marker and the body text. Example: * - *
    {@code
    - *   > [!NOTE]
    - *   Lazy body text will be parsed as a new paragraph
    - *   }
    + *
    {@code
    + * > [!NOTE]
    + * Lazy body text will be parsed as a new paragraph
    + *
    + * }
    */ -public class AlertsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, - TextContentRenderer.TextContentRendererExtension, MarkdownRenderer.MarkdownRendererExtension { - - /** - * The standard GitHub Flavored Markdown (GFM) types that the extension - * enables by default. These can be overwritten with {@link Builder#setAllowedTypes(Map)}. - */ - public static final Map STANDARD_TYPES = Map.ofEntries( - Map.entry("NOTE", "Note"), - Map.entry("TIP", "Tip"), - Map.entry("IMPORTANT", "Important"), - Map.entry("WARNING", "Warning"), - Map.entry("CAUTION", "Caution") - ); +public class AlertsExtension + implements Parser.ParserExtension, + HtmlRenderer.HtmlRendererExtension, + TextContentRenderer.TextContentRendererExtension, + MarkdownRenderer.MarkdownRendererExtension { /** - * A map of alert marker ({@code [!TYPE]}) to the default title for that marker. + * The standard GitHub Flavored Markdown (GFM) types that the extension enables by default. + * These can be overwritten with {@link Builder#setAllowedTypes(Map)}. */ + public static final Map STANDARD_TYPES = + Map.ofEntries( + Map.entry("NOTE", "Note"), + Map.entry("TIP", "Tip"), + Map.entry("IMPORTANT", "Important"), + Map.entry("WARNING", "Warning"), + Map.entry("CAUTION", "Caution")); + + /** A map of alert marker ({@code [!TYPE]}) to the default title for that marker. */ private final Map allowedTypes; + private final boolean customTitlesAllowed; private final boolean nestedAlertsAllowed; @@ -89,37 +94,39 @@ public static Builder builder() { public void extend(Parser.Builder parserBuilder) { var allowedTypesSet = new HashSet<>(allowedTypes.keySet()); parserBuilder.customBlockParserFactory( - new AlertBlockParser.Factory(allowedTypesSet, customTitlesAllowed, nestedAlertsAllowed)); + new AlertBlockParser.Factory( + allowedTypesSet, customTitlesAllowed, nestedAlertsAllowed)); } @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(context -> new AlertHtmlNodeRenderer(context, allowedTypes)); + rendererBuilder.nodeRendererFactory( + context -> new AlertHtmlNodeRenderer(context, allowedTypes)); } @Override public void extend(TextContentRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(context -> new AlertTextContentNodeRenderer(context, allowedTypes)); + rendererBuilder.nodeRendererFactory( + context -> new AlertTextContentNodeRenderer(context, allowedTypes)); } @Override public void extend(MarkdownRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { - @Override - public NodeRenderer create(MarkdownNodeRendererContext context) { - return new AlertMarkdownNodeRenderer(context); - } - - @Override - public Set getSpecialCharacters() { - return Set.of(); - } - }); + rendererBuilder.nodeRendererFactory( + new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new AlertMarkdownNodeRenderer(context); + } + + @Override + public Set getSpecialCharacters() { + return Set.of(); + } + }); } - /** - * Builder for configuring the alerts extension. - */ + /** Builder for configuring the alerts extension. */ public static class Builder { private Map allowedTypes = new HashMap<>(STANDARD_TYPES); private boolean customTitlesAllowed = false; @@ -128,12 +135,12 @@ public static class Builder { /** * Sets which alert types will be recognized and parsed into {@link Alert} blocks, * completely overwriting any previous configuration. - *

    - * By default, {@link AlertsExtension#STANDARD_TYPES} are used. * - * @param allowedTypes A map of alert type to the default title for that type. - * Must not be null/empty or contain any null/empty keys or - * values. Additionally, all alert types must be uppercase. + *

    By default, {@link AlertsExtension#STANDARD_TYPES} are used. + * + * @param allowedTypes A map of alert type to the default title for that type. Must not be + * null/empty or contain any null/empty keys or values. Additionally, all alert types + * must be uppercase. * @return {@code this} * @see Builder#addCustomType(String, String) */ @@ -153,9 +160,9 @@ public Builder setAllowedTypes(Map allowedTypes) { /** * Adds a custom alert type with a default title. - *

    - * This can also be used to override the default title of standard GFM types - * (e.g., for localization). + * + *

    This can also be used to override the default title of standard GFM types (e.g., for + * localization). * * @param type the alert type (must be uppercase) * @param title the default title for this alert type @@ -169,8 +176,8 @@ public Builder addCustomType(String type, String title) { } /** - * Allows or disallows custom titles on alerts. Inline formatting is supported - * within these titles. + * Allows or disallows custom titles on alerts. Inline formatting is supported within these + * titles. * * @param allow Whether to allow or disallow custom titles on alerts. * @return {@code this} @@ -183,11 +190,11 @@ public Builder allowCustomTitles(boolean allow) { /** * Allows or disallows parsing alerts within non-root blocks ({@code Document}). - *

    - * When disallowed, if an alert appears within another block, it will be parsed as - * a regular {@code BlockQuote}. - *

    - * Note that even when this is allowed, {@link Parser.Builder#maxOpenBlockParsers(int)} + * + *

    When disallowed, if an alert appears within another block, it will be parsed as a + * regular {@code BlockQuote}. + * + *

    Note that even when this is allowed, {@link Parser.Builder#maxOpenBlockParsers(int)} * will be respected. * * @param allow Whether to allow or disallow parsing alerts within non-root blocks. @@ -208,10 +215,7 @@ public Extension build() { /** * Checks whether an alert type and default title are valid. * - * @param type The type to validate: - *

    - * - Must not be null or empty - * - Must be uppercase + * @param type The type to validate. Must be uppercase. * @param title The default title to validate. Must not be null or empty. */ private void validateTypeAndTitle(String type, String title) { diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java index e7a9f402e..67acbe475 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertBlockParser.java @@ -4,7 +4,6 @@ import java.util.Locale; import java.util.Set; import java.util.regex.Pattern; - import org.commonmark.ext.gfm.alerts.Alert; import org.commonmark.ext.gfm.alerts.AlertTitle; import org.commonmark.node.Block; @@ -23,8 +22,10 @@ public class AlertBlockParser extends AbstractBlockParser { - private static final Pattern ALERT_PATTERN_NO_CUSTOM_TITLE = Pattern.compile("^\\[!([a-zA-Z]+)]\\s*$"); - private static final Pattern ALERT_PATTERN_CUSTOM_TITLE = Pattern.compile("^\\[!([a-zA-Z]+)](.*)$"); + private static final Pattern ALERT_PATTERN_NO_CUSTOM_TITLE = + Pattern.compile("^\\[!([a-zA-Z]+)]\\s*$"); + private static final Pattern ALERT_PATTERN_CUSTOM_TITLE = + Pattern.compile("^\\[!([a-zA-Z]+)](.*)$"); private final Alert block; private final SourceLine titleLine; @@ -103,7 +104,10 @@ public static class Factory extends AbstractBlockParserFactory { private final boolean customTitlesAllowed; private final boolean nestedAlertsAllowed; - public Factory(Set allowedTypes, boolean customTitlesAllowed, boolean nestedAlertsAllowed) { + public Factory( + Set allowedTypes, + boolean customTitlesAllowed, + boolean nestedAlertsAllowed) { this.allowedTypes = allowedTypes; this.customTitlesAllowed = customTitlesAllowed; this.nestedAlertsAllowed = nestedAlertsAllowed; @@ -116,7 +120,8 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar return BlockStart.none(); } - if (!nestedAlertsAllowed && !isAtRoot(matchedBlockParser.getMatchedBlockParser().getBlock())) { + if (!nestedAlertsAllowed + && !isAtRoot(matchedBlockParser.getMatchedBlockParser().getBlock())) { return BlockStart.none(); } @@ -176,7 +181,10 @@ private BlockStart tryStartFresh(CharSequence line, int nextNonSpace, ParserStat afterGt = state.getIndex(); } - var pattern = customTitlesAllowed ? ALERT_PATTERN_CUSTOM_TITLE : ALERT_PATTERN_NO_CUSTOM_TITLE; + var pattern = + customTitlesAllowed + ? ALERT_PATTERN_CUSTOM_TITLE + : ALERT_PATTERN_NO_CUSTOM_TITLE; var matcher = pattern.matcher(line.subSequence(afterGt, line.length())); if (!matcher.matches()) { @@ -215,7 +223,8 @@ private BlockStart tryStartFresh(CharSequence line, int nextNonSpace, ParserStat // If we got here via the promotion path, replace the empty BlockQuote. var matched = state.getActiveBlockParser().getBlock(); - if (matched instanceof BlockQuote && matched.getFirstChild() == null + if (matched instanceof BlockQuote + && matched.getFirstChild() == null && (nextNonSpace >= line.length() || line.charAt(nextNonSpace) != '>')) { start = start.replaceActiveBlockParser(); } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java index 6b9ba6da9..30aea6f04 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -1,21 +1,21 @@ package org.commonmark.ext.gfm.alerts.internal; +import java.util.LinkedHashMap; +import java.util.Map; import org.commonmark.ext.gfm.alerts.Alert; import org.commonmark.ext.gfm.alerts.AlertTitle; import org.commonmark.node.Node; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; -import java.util.LinkedHashMap; -import java.util.Map; - public class AlertHtmlNodeRenderer extends AlertNodeRenderer { private final HtmlWriter htmlWriter; private final HtmlNodeRendererContext context; private final Map allowedTypes; - public AlertHtmlNodeRenderer(HtmlNodeRendererContext context, Map allowedTypes) { + public AlertHtmlNodeRenderer( + HtmlNodeRendererContext context, Map allowedTypes) { this.htmlWriter = context.getWriter(); this.context = context; this.allowedTypes = allowedTypes; @@ -39,7 +39,8 @@ protected void renderAlert(Alert alert) { htmlWriter.line(); // Render alert title - htmlWriter.tag("p", context.extendAttributes(alert, "p", Map.of("class", "markdown-alert-title"))); + htmlWriter.tag( + "p", context.extendAttributes(alert, "p", Map.of("class", "markdown-alert-title"))); var first = alert.getFirstChild(); if (first instanceof AlertTitle) { renderChildren(first); diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java index 42e505cb7..ff4bdf6ad 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java @@ -1,12 +1,11 @@ package org.commonmark.ext.gfm.alerts.internal; +import java.util.Set; import org.commonmark.ext.gfm.alerts.Alert; import org.commonmark.ext.gfm.alerts.AlertTitle; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; -import java.util.Set; - public abstract class AlertNodeRenderer implements NodeRenderer { @Override @@ -23,8 +22,8 @@ public void render(Node node) { protected abstract void renderAlert(Alert alert); /** - * Renders the children of a parent node, excluding {@link AlertTitle} nodes. - * {@link AlertTitle} is rendered separately from other content. + * Renders the children of a parent node, excluding {@link AlertTitle} nodes. {@link AlertTitle} + * is rendered separately from other content. * * @param parent the parent node whose children should be rendered */ @@ -42,8 +41,8 @@ protected final void renderChildren(Node parent) { } /** - * Renders a single node. Subclasses must implement this to delegate - * to their context's render method. + * Renders a single node. Subclasses must implement this to delegate to their context's render + * method. * * @param node the node to render */ diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertTextContentNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertTextContentNodeRenderer.java index c14ea36a4..f494f5812 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertTextContentNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertTextContentNodeRenderer.java @@ -1,5 +1,6 @@ package org.commonmark.ext.gfm.alerts.internal; +import java.util.Map; import org.commonmark.ext.gfm.alerts.Alert; import org.commonmark.ext.gfm.alerts.AlertTitle; import org.commonmark.node.Node; @@ -7,15 +8,14 @@ import org.commonmark.renderer.text.TextContentNodeRendererContext; import org.commonmark.renderer.text.TextContentWriter; -import java.util.Map; - public class AlertTextContentNodeRenderer extends AlertNodeRenderer { private final TextContentNodeRendererContext context; private final TextContentWriter textContent; private final Map allowedTypes; - public AlertTextContentNodeRenderer(TextContentNodeRendererContext context, Map allowedTypes) { + public AlertTextContentNodeRenderer( + TextContentNodeRendererContext context, Map allowedTypes) { this.context = context; this.textContent = context.getWriter(); this.allowedTypes = allowedTypes; diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java index e31059ed2..f8e73cc06 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java @@ -1,27 +1,26 @@ package org.commonmark.ext.gfm.alerts; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.jupiter.api.Test; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - public class AlertsMarkdownRendererTest { private static final Set EXTENSIONS = Set.of(AlertsExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = + MarkdownRenderer.builder().extensions(EXTENSIONS).build(); - private static final Set EXTENSIONS_CUSTOM_TITLES = Set.of(AlertsExtension.builder().allowCustomTitles(true).build()); - private static final Parser PARSER_CUSTOM_TITLES = Parser.builder() - .extensions(EXTENSIONS_CUSTOM_TITLES) - .build(); - private static final MarkdownRenderer RENDERER_CUSTOM_TITLES = MarkdownRenderer.builder() - .extensions(EXTENSIONS_CUSTOM_TITLES) - .build(); + private static final Set EXTENSIONS_CUSTOM_TITLES = + Set.of(AlertsExtension.builder().allowCustomTitles(true).build()); + private static final Parser PARSER_CUSTOM_TITLES = + Parser.builder().extensions(EXTENSIONS_CUSTOM_TITLES).build(); + private static final MarkdownRenderer RENDERER_CUSTOM_TITLES = + MarkdownRenderer.builder().extensions(EXTENSIONS_CUSTOM_TITLES).build(); @Test public void alertRoundTrip() { @@ -61,9 +60,7 @@ public void alertWithMultipleParagraphs() { @Test public void customTypeRoundTrip() { - var extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") - .build(); + var extension = AlertsExtension.builder().addCustomType("INFO", "Information").build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = MarkdownRenderer.builder().extensions(Set.of(extension)).build(); @@ -99,14 +96,17 @@ public void customTitleRoundTrip() { @Test public void customTitleWithFormattingRoundTrip() { - assertRoundTripCustomTitles("> [!WARNING] Custom _title **with `formatting`**_\n> Be careful\n"); + assertRoundTripCustomTitles( + "> [!WARNING] Custom _title **with `formatting`**_\n> Be careful\n"); } @Test public void customTitleWithMultipleBlocks() { - var input = "> [!NOTE]Title\n> First paragraph\n>\n> Second paragraph\n>\n> - > Nested blocks\n"; + var input = + "> [!NOTE]Title\n> First paragraph\n>\n> Second paragraph\n>\n> - > Nested blocks\n"; // MarkdownWriter always writes the prefix including trailing space - var expected = "> [!NOTE] Title\n> First paragraph\n> \n> Second paragraph\n> \n> - > Nested blocks\n"; + var expected = + "> [!NOTE] Title\n> First paragraph\n> \n> Second paragraph\n> \n> - > Nested blocks\n"; var rendered = RENDERER_CUSTOM_TITLES.render(PARSER_CUSTOM_TITLES.parse(input)); assertThat(rendered).isEqualTo(expected); } diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java index d85aa1d2a..2836db3ac 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java @@ -1,5 +1,8 @@ package org.commonmark.ext.gfm.alerts; +import java.net.URL; +import java.util.List; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; @@ -11,16 +14,12 @@ import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.MethodSource; -import java.net.URL; -import java.util.List; -import java.util.Set; - /** - * Tests the default configuration of {@link AlertsExtension} against how - * GitHub Flavored Markdown (GFM) parses and renders {@link Alert}s. - *

    - * This test should only be used for the default configuration of - * {@link AlertsExtension}. Other configurations cause deviation from GFM. + * Tests the default configuration of {@link AlertsExtension} against how GitHub Flavored Markdown + * (GFM) parses and renders {@link Alert}s. + * + *

    This test should only be used for the default configuration of {@link AlertsExtension}. Other + * configurations cause deviation from GFM. */ @ParameterizedClass @MethodSource("data") @@ -28,11 +27,12 @@ public class AlertsSpecTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(AlertsExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - // Use softbreak("
    ") to match GitHub's rendering for easier comparison with GitHub API output. - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).softbreak("
    \n").build(); + // Use softbreak("
    ") to match GitHub's rendering for easier comparison with GitHub API + // output. + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).softbreak("
    \n").build(); - @Parameter - Example example; + @Parameter Example example; static List data() { URL spec = AlertsSpecTest.class.getResource("/alerts-spec.txt"); diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java index d98e0e2cf..99c0e8d7a 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -1,5 +1,11 @@ package org.commonmark.ext.gfm.alerts; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import java.util.Map; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.node.Emphasis; import org.commonmark.node.SourceSpan; @@ -11,27 +17,26 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - public class AlertsTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(AlertsExtension.create()); - private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES).build(); - private static final HtmlRenderer HTML_RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); - - private static final Set EXTENSIONS_CUSTOM_TITLES = Set.of(AlertsExtension.builder().allowCustomTitles(true).build()); - private static final Parser PARSER_CUSTOM_TITLES = Parser.builder() - .extensions(EXTENSIONS_CUSTOM_TITLES) - .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) - .build(); - private static final HtmlRenderer HTML_RENDERER_CUSTOM_TITLES = HtmlRenderer.builder() - .extensions(EXTENSIONS_CUSTOM_TITLES) - .build(); + private static final Parser PARSER = + Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); + private static final HtmlRenderer HTML_RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).build(); + + private static final Set EXTENSIONS_CUSTOM_TITLES = + Set.of(AlertsExtension.builder().allowCustomTitles(true).build()); + private static final Parser PARSER_CUSTOM_TITLES = + Parser.builder() + .extensions(EXTENSIONS_CUSTOM_TITLES) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); + private static final HtmlRenderer HTML_RENDERER_CUSTOM_TITLES = + HtmlRenderer.builder().extensions(EXTENSIONS_CUSTOM_TITLES).build(); @Override protected String render(String source) { @@ -39,103 +44,109 @@ protected String render(String source) { } private void assertRenderingCustomTitles(String source, String expectedResult) { - assertThat(HTML_RENDERER_CUSTOM_TITLES.render(PARSER_CUSTOM_TITLES.parse(source))).isEqualTo(expectedResult); + assertThat(HTML_RENDERER_CUSTOM_TITLES.render(PARSER_CUSTOM_TITLES.parse(source))) + .isEqualTo(expectedResult); } // Custom types @Test public void customType() { - var extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") - .build(); + var extension = AlertsExtension.builder().addCustomType("INFO", "Information").build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); - assertThat(renderer.render(parser.parse("> [!INFO]\n> Custom alert"))).isEqualTo( - "

    \n" + - "

    Information

    \n" + - "

    Custom alert

    \n" + - "
    \n"); + assertThat(renderer.render(parser.parse("> [!INFO]\n> Custom alert"))) + .isEqualTo( + "
    \n" + + "

    Information

    \n" + + "

    Custom alert

    \n" + + "
    \n"); } @Test public void multipleCustomTypes() { - var extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") - .addCustomType("SUCCESS", "Success!") - .addCustomType("DANGER", "Danger!") - .build(); + var extension = + AlertsExtension.builder() + .addCustomType("INFO", "Information") + .addCustomType("SUCCESS", "Success!") + .addCustomType("DANGER", "Danger!") + .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); - assertThat(renderer.render(parser.parse("> [!INFO]\n> Info content\n\n> [!SUCCESS]\n> Success content\n\n> [!DANGER]\n> Danger content"))).isEqualTo( - "
    \n" + - "

    Information

    \n" + - "

    Info content

    \n" + - "
    \n" + - "
    \n" + - "

    Success!

    \n" + - "

    Success content

    \n" + - "
    \n" + - "
    \n" + - "

    Danger!

    \n" + - "

    Danger content

    \n" + - "
    \n"); + assertThat( + renderer.render( + parser.parse( + "> [!INFO]\n> Info content\n\n> [!SUCCESS]\n> Success content\n\n> [!DANGER]\n> Danger content"))) + .isEqualTo( + "
    \n" + + "

    Information

    \n" + + "

    Info content

    \n" + + "
    \n" + + "
    \n" + + "

    Success!

    \n" + + "

    Success content

    \n" + + "
    \n" + + "
    \n" + + "

    Danger!

    \n" + + "

    Danger content

    \n" + + "
    \n"); } @Test public void standardTypesWithCustomConfigured() { - var extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") - .build(); + var extension = AlertsExtension.builder().addCustomType("INFO", "Information").build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); - assertThat(renderer.render(parser.parse("> [!NOTE]\n> Standard type"))).isEqualTo( - "
    \n" + - "

    Note

    \n" + - "

    Standard type

    \n" + - "
    \n"); + assertThat(renderer.render(parser.parse("> [!NOTE]\n> Standard type"))) + .isEqualTo( + "
    \n" + + "

    Note

    \n" + + "

    Standard type

    \n" + + "
    \n"); } @Test public void overrideStandardTypeTitle() { - var extension = AlertsExtension.builder() - .addCustomType("NOTE", "Nota") - .build(); + var extension = AlertsExtension.builder().addCustomType("NOTE", "Nota").build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); - assertThat(renderer.render(parser.parse("> [!NOTE]\n> Localized title"))).isEqualTo( - "
    \n" + - "

    Nota

    \n" + - "

    Localized title

    \n" + - "
    \n"); + assertThat(renderer.render(parser.parse("> [!NOTE]\n> Localized title"))) + .isEqualTo( + "
    \n" + + "

    Nota

    \n" + + "

    Localized title

    \n" + + "
    \n"); } // Custom type validation @Test public void customTypeMustBeUppercase() { - assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().addCustomType("info", "Information").build()); + assertThrows( + IllegalArgumentException.class, + () -> AlertsExtension.builder().addCustomType("info", "Information").build()); } @Test public void customTypeMustNotBeEmpty() { - assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().addCustomType("", "Title").build()); + assertThrows( + IllegalArgumentException.class, + () -> AlertsExtension.builder().addCustomType("", "Title").build()); } @Test public void customTypeTitleMustNotBeEmpty() { - assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().addCustomType("INFO", "").build()); + assertThrows( + IllegalArgumentException.class, + () -> AlertsExtension.builder().addCustomType("INFO", "").build()); } // Overwriting types @@ -143,36 +154,41 @@ public void customTypeTitleMustNotBeEmpty() { @Test public void overwriteStandardTypes() { var allowedTypes = Map.ofEntries(Map.entry("IMPORTANT", "Important")); - var extension = AlertsExtension.builder() - .setAllowedTypes(allowedTypes) - .addCustomType("BUG", "Known Bug") - .build(); + var extension = + AlertsExtension.builder() + .setAllowedTypes(allowedTypes) + .addCustomType("BUG", "Known Bug") + .build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); - assertThat(renderer.render(parser.parse("> [!NOTE]\n> Regular block quote"))).isEqualTo( - "
    \n" + - "

    [!NOTE]\n" + - "Regular block quote

    \n" + - "
    \n"); - - assertThat(renderer.render(parser.parse("> [!TIP]\n> Regular block quote"))).isEqualTo( - "
    \n" + - "

    [!TIP]\n" + - "Regular block quote

    \n" + - "
    \n"); - - assertThat(renderer.render(parser.parse("> [!IMPORTANT]\n> Alert"))).isEqualTo( - "
    \n" + - "

    Important

    \n" + - "

    Alert

    \n" + - "
    \n"); - - assertThat(renderer.render(parser.parse("> [!BUG]\n> Alert"))).isEqualTo( - "
    \n" + - "

    Known Bug

    \n" + - "

    Alert

    \n" + - "
    \n"); + assertThat(renderer.render(parser.parse("> [!NOTE]\n> Regular block quote"))) + .isEqualTo( + "
    \n" + + "

    [!NOTE]\n" + + "Regular block quote

    \n" + + "
    \n"); + + assertThat(renderer.render(parser.parse("> [!TIP]\n> Regular block quote"))) + .isEqualTo( + "
    \n" + + "

    [!TIP]\n" + + "Regular block quote

    \n" + + "
    \n"); + + assertThat(renderer.render(parser.parse("> [!IMPORTANT]\n> Alert"))) + .isEqualTo( + "
    \n" + + "

    Important

    \n" + + "

    Alert

    \n" + + "
    \n"); + + assertThat(renderer.render(parser.parse("> [!BUG]\n> Alert"))) + .isEqualTo( + "
    \n" + + "

    Known Bug

    \n" + + "

    Alert

    \n" + + "
    \n"); } // Overwriting types validation @@ -180,226 +196,249 @@ public void overwriteStandardTypes() { @Test public void overwriteTypesMustBeUppercase() { var allowedTypes = Map.ofEntries(Map.entry("info", "Info")); - assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().setAllowedTypes(allowedTypes).build()); + assertThrows( + IllegalArgumentException.class, + () -> AlertsExtension.builder().setAllowedTypes(allowedTypes).build()); } @Test public void overwriteTypesMustNotBeEmpty() { var allowedTypes = Map.ofEntries(Map.entry("", "Info")); - assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().setAllowedTypes(allowedTypes).build()); + assertThrows( + IllegalArgumentException.class, + () -> AlertsExtension.builder().setAllowedTypes(allowedTypes).build()); } @Test public void overwriteTypesTitleMustNotBeEmpty() { var allowedTypes = Map.ofEntries(Map.entry("INFO", "")); - assertThrows(IllegalArgumentException.class, () -> - AlertsExtension.builder().setAllowedTypes(allowedTypes).build()); + assertThrows( + IllegalArgumentException.class, + () -> AlertsExtension.builder().setAllowedTypes(allowedTypes).build()); } // Custom titles @Test public void customTitle() { - assertRenderingCustomTitles("> [!NOTE] Custom title\n> Note with a custom title", - "
    \n" + - "

    Custom title

    \n" + - "

    Note with a custom title

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!NOTE] Custom title\n> Note with a custom title", + "
    \n" + + "

    Custom title

    \n" + + "

    Note with a custom title

    \n" + + "
    \n"); } @Test public void customTitleNoSpace() { - assertRenderingCustomTitles("> [!NOTE]Custom title\n> Note with a custom title", - "
    \n" + - "

    Custom title

    \n" + - "

    Note with a custom title

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!NOTE]Custom title\n> Note with a custom title", + "
    \n" + + "

    Custom title

    \n" + + "

    Note with a custom title

    \n" + + "
    \n"); } @Test public void customTitleExtraSpace() { - assertRenderingCustomTitles("> [!NOTE] Custom title \n> Note with a custom title", - "
    \n" + - "

    Custom title

    \n" + - "

    Note with a custom title

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!NOTE] Custom title \n> Note with a custom title", + "
    \n" + + "

    Custom title

    \n" + + "

    Note with a custom title

    \n" + + "
    \n"); } @Test public void customTitleNoHardLineBreak() { - assertRenderingCustomTitles("> [!NOTE] Custom title\\\n> Note with a custom title", - "
    \n" + - "

    Custom title\\

    \n" + - "

    Note with a custom title

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!NOTE] Custom title\\\n> Note with a custom title", + "
    \n" + + "

    Custom title\\

    \n" + + "

    Note with a custom title

    \n" + + "
    \n"); } @Test public void customTitleWithComment() { - assertRenderingCustomTitles("> [!NOTE] Custom title \n> Note with a custom title", - "
    \n" + - "

    Custom title

    \n" + - "

    Note with a custom title

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!NOTE] Custom title \n> Note with a custom title", + "
    \n" + + "

    Custom title

    \n" + + "

    Note with a custom title

    \n" + + "
    \n"); } @Test public void customTitleWithInlineFormatting() { - assertRenderingCustomTitles("> [!NOTE] Custom _title with **formatting**_\n> Note with a custom title", - "
    \n" + - "

    Custom title with formatting

    \n" + - "

    Note with a custom title

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!NOTE] Custom _title with **formatting**_\n> Note with a custom title", + "
    \n" + + "

    Custom title with formatting

    \n" + + "

    Note with a custom title

    \n" + + "
    \n"); } @Test public void customTitleWithLinkAndCode() { - assertRenderingCustomTitles("> [!IMPORTANT] See [docs](https://example.com) or `run()`\n> Note with a custom title", - "
    \n" + - "

    See docs or run()

    \n" + - "

    Note with a custom title

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!IMPORTANT] See [docs](https://example.com) or `run()`\n> Note with a custom title", + "
    \n" + + "

    See docs or run()

    \n" + + "

    Note with a custom title

    \n" + + "
    \n"); } @Test public void customTitleLeadingEmptyLines() { - assertRenderingCustomTitles(">\n> \n> [!NOTE] Custom **title**\n> Note with a custom title", - "
    \n" + - "

    Custom title

    \n" + - "

    Note with a custom title

    \n" + - "
    \n"); + assertRenderingCustomTitles( + ">\n> \n> [!NOTE] Custom **title**\n> Note with a custom title", + "
    \n" + + "

    Custom title

    \n" + + "

    Note with a custom title

    \n" + + "
    \n"); } @Test public void customTitleNoOverlappingInlines() { - assertRenderingCustomTitles("> [!NOTE] Custom _title with **opening `delimiters\n> Note` with** closing delimiters_", - "
    \n" + - "

    Custom _title with **opening `delimiters

    \n" + - "

    Note` with** closing delimiters_

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!NOTE] Custom _title with **opening `delimiters\n> Note` with** closing delimiters_", + "
    \n" + + "

    Custom _title with **opening `delimiters

    \n" + + "

    Note` with** closing delimiters_

    \n" + + "
    \n"); } @Test public void customTitleIgnoresBlockContent() { - assertRenderingCustomTitles("> [!NOTE] ## Custom title looks like an ATX heading\n>But it's not", - "
    \n" + - "

    ## Custom title looks like an ATX heading

    \n" + - "

    But it's not

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!NOTE] ## Custom title looks like an ATX heading\n>But it's not", + "
    \n" + + "

    ## Custom title looks like an ATX heading

    \n" + + "

    But it's not

    \n" + + "
    \n"); } @Test public void customTitleNoBody() { // Alerts with no body are allowed. - assertRenderingCustomTitles("> [!NOTE] Custom _title_\n> \n>\n>", - "
    \n" + - "

    Custom title

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!NOTE] Custom _title_\n> \n>\n>", + "
    \n" + + "

    Custom title

    \n" + + "
    \n"); } @Test public void customTitleNoBodyNoSpace() { - assertRenderingCustomTitles("> [!NOTE] Custom _title_", - "
    \n" + - "

    Custom title

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!NOTE] Custom _title_", + "
    \n" + + "

    Custom title

    \n" + + "
    \n"); } @Test public void onlyTrailingWhitespaceIsNotCustomTitle() { - assertRenderingCustomTitles("> [!NOTE] \n> Body text", - "
    \n" + - "

    Note

    \n" + - "

    Body text

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!NOTE] \n> Body text", + "
    \n" + + "

    Note

    \n" + + "

    Body text

    \n" + + "
    \n"); } // Lazy continuation @Test public void noLazyContinuationAfterMarker() { - assertRendering("> [!NOTE]\nBody text", - "
    \n" + - "

    Note

    \n" + - "
    \n" + - "

    Body text

    \n"); + assertRendering( + "> [!NOTE]\nBody text", + "
    \n" + + "

    Note

    \n" + + "
    \n" + + "

    Body text

    \n"); } @Test public void noLazyContinuationAfterTitle() { - assertRenderingCustomTitles("> [!NOTE] Custom title\nBody text", - "
    \n" + - "

    Custom title

    \n" + - "
    \n" + - "

    Body text

    \n"); + assertRenderingCustomTitles( + "> [!NOTE] Custom title\nBody text", + "
    \n" + + "

    Custom title

    \n" + + "
    \n" + + "

    Body text

    \n"); } // Alert markers take precedence over link reference definitions @Test public void alertTakesPrecedence() { - assertRenderingCustomTitles("> [!NOTE]: https://example.com\n> Body text\n\n[!NOTE]", - "
    \n" + - "

    : https://example.com

    \n" + - "

    Body text

    \n" + - "
    \n" + - "

    [!NOTE]

    \n"); + assertRenderingCustomTitles( + "> [!NOTE]: https://example.com\n> Body text\n\n[!NOTE]", + "
    \n" + + "

    : https://example.com

    \n" + + "

    Body text

    \n" + + "
    \n" + + "

    [!NOTE]

    \n"); } @Test public void alertTakesPrecedenceBefore() { - assertRenderingCustomTitles("> [!NOTE]\n> Body text\n\n[!NOTE]: https://example.com", - "
    \n" + - "

    Note

    \n" + - "

    Body text

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "> [!NOTE]\n> Body text\n\n[!NOTE]: https://example.com", + "
    \n" + + "

    Note

    \n" + + "

    Body text

    \n" + + "
    \n"); } @Test public void alertTakesPrecedenceAfter() { - assertRenderingCustomTitles("[!NOTE]: https://example.com\n\n> [!NOTE]\n> Body text", - "
    \n" + - "

    Note

    \n" + - "

    Body text

    \n" + - "
    \n"); + assertRenderingCustomTitles( + "[!NOTE]: https://example.com\n\n> [!NOTE]\n> Body text", + "
    \n" + + "

    Note

    \n" + + "

    Body text

    \n" + + "
    \n"); } // Nested alerts @Test public void noNestedAlertsByDefault() { - assertRendering("> [!TIP]\n> Body\n\n- > [!TIP]\n > Nested body", - "
    \n" + - "

    Tip

    \n" + - "

    Body

    \n" + - "
    \n" + - "
      \n" + - "
    • \n" + - "
      \n" + - "

      [!TIP]\n" + - "Nested body

      \n" + - "
      \n" + - "
    • \n" + - "
    \n"); + assertRendering( + "> [!TIP]\n> Body\n\n- > [!TIP]\n > Nested body", + "
    \n" + + "

    Tip

    \n" + + "

    Body

    \n" + + "
    \n" + + "
      \n" + + "
    • \n" + + "
      \n" + + "

      [!TIP]\n" + + "Nested body

      \n" + + "
      \n" + + "
    • \n" + + "
    \n"); } @Test public void noNestedAlertsByDefaultLeadingEmptyLines() { - assertRendering(">\n> \n> [!TIP]\n> Body\n\n- > [!TIP]\n > Nested body", - "
    \n" + - "

    Tip

    \n" + - "

    Body

    \n" + - "
    \n" + - "
      \n" + - "
    • \n" + - "
      \n" + - "

      [!TIP]\n" + - "Nested body

      \n" + - "
      \n" + - "
    • \n" + - "
    \n"); + assertRendering( + ">\n> \n> [!TIP]\n> Body\n\n- > [!TIP]\n > Nested body", + "
    \n" + + "

    Tip

    \n" + + "

    Body

    \n" + + "
    \n" + + "
      \n" + + "
    • \n" + + "
      \n" + + "

      [!TIP]\n" + + "Nested body

      \n" + + "
      \n" + + "
    • \n" + + "
    \n"); } @Test @@ -408,46 +447,50 @@ public void nestedAlerts() { var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); - var source = String.join("\n", - "> [!TIP]", - "> Tip body", - "> > [!IMPORTANT]", - "> > Important body", - "", - "- > [!NOTE]", - " > Nested body", - " >", - " > 1. Ordered list body", - " >", - " > > [!CAUTION]", - " > >", - " > > Deeply nested body"); - var expected = String.join("\n", - "
    ", - "

    Tip

    ", - "

    Tip body

    ", - "
    ", - "

    Important

    ", - "

    Important body

    ", - "
    ", - "
    ", - "
      ", - "
    • ", - "
      ", - "

      Note

      ", - "

      Nested body

      ", - "
        ", - "
      1. ", - "

        Ordered list body

        ", - "
        ", - "

        Caution

        ", - "

        Deeply nested body

        ", - "
        ", - "
      2. ", - "
      ", - "
      ", - "
    • ", - "
    \n"); + var source = + String.join( + "\n", + "> [!TIP]", + "> Tip body", + "> > [!IMPORTANT]", + "> > Important body", + "", + "- > [!NOTE]", + " > Nested body", + " >", + " > 1. Ordered list body", + " >", + " > > [!CAUTION]", + " > >", + " > > Deeply nested body"); + var expected = + String.join( + "\n", + "
    ", + "

    Tip

    ", + "

    Tip body

    ", + "
    ", + "

    Important

    ", + "

    Important body

    ", + "
    ", + "
    ", + "
      ", + "
    • ", + "
      ", + "

      Note

      ", + "

      Nested body

      ", + "
        ", + "
      1. ", + "

        Ordered list body

        ", + "
        ", + "

        Caution

        ", + "

        Deeply nested body

        ", + "
        ", + "
      2. ", + "
      ", + "
      ", + "
    • ", + "
    \n"); assertThat(renderer.render(parser.parse(source))).isEqualTo(expected); } @@ -465,9 +508,7 @@ public void alertParsedAsAlertNode() { @Test public void customTypeParsedAsAlertNode() { - var extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") - .build(); + var extension = AlertsExtension.builder().addCustomType("INFO", "Information").build(); var parser = Parser.builder().extensions(Set.of(extension)).build(); @@ -578,5 +619,4 @@ public void titleWithNestedInlineFormattingSourcePosition() { assertThat(((Text) italicText).getLiteral()).isEqualTo("and italic"); assertThat(italicText.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 28, 28, 10))); } - } diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTextContentRendererTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTextContentRendererTest.java index 9bc69677c..3979b0488 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTextContentRendererTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTextContentRendererTest.java @@ -1,5 +1,6 @@ package org.commonmark.ext.gfm.alerts; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.text.LineBreakRendering; @@ -7,33 +8,45 @@ import org.commonmark.testutil.Asserts; import org.junit.jupiter.api.Test; -import java.util.Set; - public class AlertsTextContentRendererTest { private static final Set EXTENSIONS = Set.of(AlertsExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final TextContentRenderer COMPACT_RENDERER = TextContentRenderer.builder() - .extensions(EXTENSIONS).build(); - private static final TextContentRenderer SEPARATE_RENDERER = TextContentRenderer.builder() - .extensions(EXTENSIONS).lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS).build(); - private static final TextContentRenderer STRIPPED_RENDERER = TextContentRenderer.builder() - .extensions(EXTENSIONS).lineBreakRendering(LineBreakRendering.STRIP).build(); - - private static final Set EXTENSIONS_CUSTOM_TITLES = Set.of( - AlertsExtension.builder().allowCustomTitles(true).allowNestedAlerts(true).build()); - private static final Parser PARSER_CUSTOM_TITLES = Parser.builder() - .extensions(EXTENSIONS_CUSTOM_TITLES).build(); - - private static final TextContentRenderer COMPACT_RENDERER_CUSTOM = TextContentRenderer.builder() - .extensions(EXTENSIONS_CUSTOM_TITLES).build(); - private static final TextContentRenderer SEPARATE_RENDERER_CUSTOM = TextContentRenderer.builder() - .extensions(EXTENSIONS_CUSTOM_TITLES) - .lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS).build(); - private static final TextContentRenderer STRIPPED_RENDERER_CUSTOM = TextContentRenderer.builder() - .extensions(EXTENSIONS_CUSTOM_TITLES) - .lineBreakRendering(LineBreakRendering.STRIP).build(); + private static final TextContentRenderer COMPACT_RENDERER = + TextContentRenderer.builder().extensions(EXTENSIONS).build(); + private static final TextContentRenderer SEPARATE_RENDERER = + TextContentRenderer.builder() + .extensions(EXTENSIONS) + .lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS) + .build(); + private static final TextContentRenderer STRIPPED_RENDERER = + TextContentRenderer.builder() + .extensions(EXTENSIONS) + .lineBreakRendering(LineBreakRendering.STRIP) + .build(); + + private static final Set EXTENSIONS_CUSTOM_TITLES = + Set.of( + AlertsExtension.builder() + .allowCustomTitles(true) + .allowNestedAlerts(true) + .build()); + private static final Parser PARSER_CUSTOM_TITLES = + Parser.builder().extensions(EXTENSIONS_CUSTOM_TITLES).build(); + + private static final TextContentRenderer COMPACT_RENDERER_CUSTOM = + TextContentRenderer.builder().extensions(EXTENSIONS_CUSTOM_TITLES).build(); + private static final TextContentRenderer SEPARATE_RENDERER_CUSTOM = + TextContentRenderer.builder() + .extensions(EXTENSIONS_CUSTOM_TITLES) + .lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS) + .build(); + private static final TextContentRenderer STRIPPED_RENDERER_CUSTOM = + TextContentRenderer.builder() + .extensions(EXTENSIONS_CUSTOM_TITLES) + .lineBreakRendering(LineBreakRendering.STRIP) + .build(); @Test public void alertNoBody() { @@ -123,7 +136,8 @@ public void alertWithinBlockQuote() { public void alertWithinAlert() { var source = "> [!NOTE] Custom title\n> Body text\n> > [!WARNING]\n> > Nested body text"; assertCompactCustomTitles(source, "Custom title\nBody text\nWarning\nNested body text"); - assertSeparateCustomTitles(source, "Custom title\n\nBody text\n\nWarning\n\nNested body text"); + assertSeparateCustomTitles( + source, "Custom title\n\nBody text\n\nWarning\n\nNested body text"); assertStrippedCustomTitles(source, "Custom title: Body text Warning: Nested body text"); } diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java index 34b78385c..26b348016 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java @@ -1,14 +1,11 @@ package org.commonmark.ext.gfm.alerts.examples; +import java.util.List; import org.commonmark.ext.gfm.alerts.AlertsExtension; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; -import java.util.List; - -/** - * Example demonstrating the use of the GFM Alerts extension. - */ +/** Example demonstrating the use of the GFM Alerts extension. */ public class AlertsExample { public static void main(String[] args) { @@ -23,25 +20,22 @@ private static void standardTypesExample() { var extension = AlertsExtension.create(); - var parser = Parser.builder() - .extensions(List.of(extension)) - .build(); - - var renderer = HtmlRenderer.builder() - .extensions(List.of(extension)) - .build(); - - var markdown = "# GFM Alerts Demo\n\n" + - "> [!NOTE]\n" + - "> Highlights information that users should take into account.\n\n" + - "> [!TIP]\n" + - "> Helpful advice for doing things better.\n\n" + - "> [!IMPORTANT]\n" + - "> Key information users need to know.\n\n" + - "> [!WARNING]\n" + - "> Urgent info that needs immediate attention.\n\n" + - "> [!CAUTION]\n" + - "> Advises about risks or negative outcomes.\n"; + var parser = Parser.builder().extensions(List.of(extension)).build(); + + var renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); + + var markdown = + "# GFM Alerts Demo\n\n" + + "> [!NOTE]\n" + + "> Highlights information that users should take into account.\n\n" + + "> [!TIP]\n" + + "> Helpful advice for doing things better.\n\n" + + "> [!IMPORTANT]\n" + + "> Key information users need to know.\n\n" + + "> [!WARNING]\n" + + "> Urgent info that needs immediate attention.\n\n" + + "> [!CAUTION]\n" + + "> Advises about risks or negative outcomes.\n"; var html = renderer.render(parser.parse(markdown)); @@ -55,25 +49,20 @@ private static void customTypesExample() { System.out.println("CUSTOM ALERT TYPES"); System.out.println("=".repeat(60)); - var extension = AlertsExtension.builder() - .addCustomType("BUG", "Known Bug") - .build(); - - var parser = Parser.builder() - .extensions(List.of(extension)) - .build(); - - var renderer = HtmlRenderer.builder() - .extensions(List.of(extension)) - .build(); - - var markdown = "# Custom Alert Types\n\n" + - "> [!NOTE]\n" + - "> Useful information that users should know.\n\n" + - "> [!TIP]\n" + - "> Helpful advice for doing things better.\n\n" + - "> [!BUG]\n" + - "> This feature has a known issue with large files (see #42).\n"; + var extension = AlertsExtension.builder().addCustomType("BUG", "Known Bug").build(); + + var parser = Parser.builder().extensions(List.of(extension)).build(); + + var renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); + + var markdown = + "# Custom Alert Types\n\n" + + "> [!NOTE]\n" + + "> Useful information that users should know.\n\n" + + "> [!TIP]\n" + + "> Helpful advice for doing things better.\n\n" + + "> [!BUG]\n" + + "> This feature has a known issue with large files (see #42).\n"; var html = renderer.render(parser.parse(markdown)); diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/Strikethrough.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/Strikethrough.java index 0c24642bc..76ce213e8 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/Strikethrough.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/Strikethrough.java @@ -3,9 +3,7 @@ import org.commonmark.node.CustomNode; import org.commonmark.node.Delimited; -/** - * A strikethrough node containing text and other inline nodes as children. - */ +/** A strikethrough node containing text and other inline nodes as children. */ public class Strikethrough extends CustomNode implements Delimited { private String delimiter; diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java index 0571aed84..4b18b7d66 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java @@ -1,5 +1,6 @@ package org.commonmark.ext.gfm.strikethrough; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughDelimiterProcessor; import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughHtmlNodeRenderer; @@ -13,38 +14,40 @@ import org.commonmark.renderer.markdown.MarkdownRenderer; import org.commonmark.renderer.text.TextContentRenderer; -import java.util.Set; - /** * Extension for GFM strikethrough using {@code ~} or {@code ~~} (GitHub Flavored Markdown). - *

    Example input:

    + * + *

    Example input: + * *

    {@code ~foo~ or ~~bar~~}
    - *

    Example output (HTML):

    + * + *

    Example output (HTML): + * *

    {@code foo or bar}
    - *

    - * Create the extension with {@link #create()} and then add it to the parser and renderer builders - * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, - * {@link HtmlRenderer.Builder#extensions(Iterable)}). - *

    - *

    - * The parsed strikethrough text regions are turned into {@link Strikethrough} nodes. - *

    - *

    - * If you have another extension that only uses a single tilde ({@code ~}) syntax, you will have to configure this - * {@link StrikethroughExtension} to only accept the double tilde syntax, like this: - *

    - *
    - *     {@code
    - *     StrikethroughExtension.builder().requireTwoTildes(true).build();
    - *     }
    - * 
    - *

    - * If you don't do that, there's a conflict between the two extensions and you will get an - * {@link IllegalArgumentException} when constructing the parser. - *

    + * + *

    Create the extension with {@link #create()} and then add it to the parser and renderer + * builders ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, {@link + * HtmlRenderer.Builder#extensions(Iterable)}). + * + *

    The parsed strikethrough text regions are turned into {@link Strikethrough} nodes. + * + *

    If you have another extension that only uses a single tilde ({@code ~}) syntax, you will have + * to configure this {@link StrikethroughExtension} to only accept the double tilde syntax, like + * this: + * + *

    {@code
    + * StrikethroughExtension.builder().requireTwoTildes(true).build();
    + *
    + * }
    + * + *

    If you don't do that, there's a conflict between the two extensions and you will get an {@link + * IllegalArgumentException} when constructing the parser. */ -public class StrikethroughExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, - TextContentRenderer.TextContentRendererExtension, MarkdownRenderer.MarkdownRendererExtension { +public class StrikethroughExtension + implements Parser.ParserExtension, + HtmlRenderer.HtmlRendererExtension, + TextContentRenderer.TextContentRendererExtension, + MarkdownRenderer.MarkdownRendererExtension { private final boolean requireTwoTildes; @@ -68,7 +71,8 @@ public static Builder builder() { @Override public void extend(Parser.Builder parserBuilder) { - parserBuilder.customDelimiterProcessor(new StrikethroughDelimiterProcessor(requireTwoTildes)); + parserBuilder.customDelimiterProcessor( + new StrikethroughDelimiterProcessor(requireTwoTildes)); } @Override @@ -83,17 +87,18 @@ public void extend(TextContentRenderer.Builder rendererBuilder) { @Override public void extend(MarkdownRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { - @Override - public NodeRenderer create(MarkdownNodeRendererContext context) { - return new StrikethroughMarkdownNodeRenderer(context); - } - - @Override - public Set getSpecialCharacters() { - return Set.of('~'); - } - }); + rendererBuilder.nodeRendererFactory( + new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new StrikethroughMarkdownNodeRenderer(context); + } + + @Override + public Set getSpecialCharacters() { + return Set.of('~'); + } + }); } public static class Builder { @@ -101,8 +106,9 @@ public static class Builder { private boolean requireTwoTildes = false; /** - * @param requireTwoTildes Whether two tilde characters ({@code ~~}) are required for strikethrough or whether - * one is also enough. Default is {@code false}; both a single tilde and two tildes can be used for strikethrough. + * @param requireTwoTildes Whether two tilde characters ({@code ~~}) are required for + * strikethrough or whether one is also enough. Default is {@code false}; both a single + * tilde and two tildes can be used for strikethrough. * @return {@code this} */ public Builder requireTwoTildes(boolean requireTwoTildes) { diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java index 4657106ab..692b35f67 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java @@ -43,7 +43,10 @@ public int process(DelimiterRun openingRun, DelimiterRun closingRun) { Text opener = openingRun.getOpener(); // Wrap nodes between delimiters in strikethrough. - String delimiter = openingRun.length() == 1 ? opener.getLiteral() : opener.getLiteral() + opener.getLiteral(); + String delimiter = + openingRun.length() == 1 + ? opener.getLiteral() + : opener.getLiteral() + opener.getLiteral(); Node strikethrough = new Strikethrough(delimiter); SourceSpans sourceSpans = new SourceSpans(); diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlNodeRenderer.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlNodeRenderer.java index b1a82cb03..e5ee31f4e 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlNodeRenderer.java @@ -1,11 +1,10 @@ package org.commonmark.ext.gfm.strikethrough.internal; +import java.util.Map; import org.commonmark.node.Node; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; -import java.util.Map; - public class StrikethroughHtmlNodeRenderer extends StrikethroughNodeRenderer { private final HtmlNodeRendererContext context; diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughNodeRenderer.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughNodeRenderer.java index 18ed21887..56d94d300 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughNodeRenderer.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughNodeRenderer.java @@ -1,11 +1,10 @@ package org.commonmark.ext.gfm.strikethrough.internal; +import java.util.Set; import org.commonmark.ext.gfm.strikethrough.Strikethrough; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; -import java.util.Set; - abstract class StrikethroughNodeRenderer implements NodeRenderer { @Override diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java index ebdcd9dbe..a5d6a7e31 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java @@ -1,8 +1,8 @@ package org.commonmark.ext.gfm.strikethrough.internal; -import org.commonmark.renderer.text.TextContentWriter; -import org.commonmark.renderer.text.TextContentNodeRendererContext; import org.commonmark.node.Node; +import org.commonmark.renderer.text.TextContentNodeRendererContext; +import org.commonmark.renderer.text.TextContentWriter; public class StrikethroughTextContentNodeRenderer extends StrikethroughNodeRenderer { diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java index c497a4db3..2d5a6a88d 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughMarkdownRendererTest.java @@ -1,19 +1,19 @@ package org.commonmark.ext.gfm.strikethrough; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.jupiter.api.Test; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - public class StrikethroughMarkdownRendererTest { private static final Set EXTENSIONS = Set.of(StrikethroughExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = + MarkdownRenderer.builder().extensions(EXTENSIONS).build(); @Test public void testStrikethrough() { diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java index f1199b521..d63a83b51 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java @@ -1,5 +1,7 @@ package org.commonmark.ext.gfm.strikethrough; +import java.util.List; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; @@ -12,19 +14,16 @@ import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.MethodSource; -import java.util.List; -import java.util.Set; - @ParameterizedClass @MethodSource("data") public class StrikethroughSpecTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(StrikethroughExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).build(); - @Parameter - Example example; + @Parameter Example example; static List data() { return ExampleReader.readExamples(TestResources.getGfmSpec(), "strikethrough"); diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java index c29391cdd..10731ca7e 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java @@ -1,5 +1,9 @@ package org.commonmark.ext.gfm.strikethrough; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.node.Node; import org.commonmark.node.Paragraph; @@ -14,18 +18,14 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - public class StrikethroughTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(StrikethroughExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final HtmlRenderer HTML_RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); - private static final TextContentRenderer CONTENT_RENDERER = TextContentRenderer.builder() - .extensions(EXTENSIONS).build(); + private static final HtmlRenderer HTML_RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final TextContentRenderer CONTENT_RENDERER = + TextContentRenderer.builder().extensions(EXTENSIONS).build(); @Test public void oneTildeIsEnough() { @@ -70,13 +70,15 @@ public void tildesInside() { @Test public void strikethroughWholeParagraphWithOtherDelimiters() { - assertRendering("~~Paragraph with *emphasis* and __strong emphasis__~~", + assertRendering( + "~~Paragraph with *emphasis* and __strong emphasis__~~", "

    Paragraph with emphasis and strong emphasis

    \n"); } @Test public void insideBlockQuote() { - assertRendering("> strike ~~that~~", + assertRendering( + "> strike ~~that~~", "
    \n

    strike that

    \n
    \n"); } @@ -96,12 +98,15 @@ public void textContentRenderer() { @Test public void requireTwoTildesOption() { - Parser parser = Parser.builder() - .extensions(Set.of(StrikethroughExtension.builder() - .requireTwoTildes(true) - .build())) - .customDelimiterProcessor(new SubscriptDelimiterProcessor()) - .build(); + Parser parser = + Parser.builder() + .extensions( + Set.of( + StrikethroughExtension.builder() + .requireTwoTildes(true) + .build())) + .customDelimiterProcessor(new SubscriptDelimiterProcessor()) + .build(); Node document = parser.parse("~foo~ ~~bar~~"); assertThat(CONTENT_RENDERER.render(document)).isEqualTo("(sub)foo(/sub) /bar/"); @@ -109,10 +114,11 @@ public void requireTwoTildesOption() { @Test public void sourceSpans() { - Parser parser = Parser.builder() - .extensions(EXTENSIONS) - .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) - .build(); + Parser parser = + Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); Node document = parser.parse("hey ~~there~~\n"); Paragraph block = (Paragraph) document.getFirstChild(); diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBlock.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBlock.java index c46fc27ef..3ad550907 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBlock.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBlock.java @@ -2,8 +2,5 @@ import org.commonmark.node.CustomBlock; -/** - * Table block containing a {@link TableHead} and optionally a {@link TableBody}. - */ -public class TableBlock extends CustomBlock { -} +/** Table block containing a {@link TableHead} and optionally a {@link TableBody}. */ +public class TableBlock extends CustomBlock {} diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBody.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBody.java index ddc80deb3..09f5b45b1 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBody.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBody.java @@ -2,8 +2,5 @@ import org.commonmark.node.CustomNode; -/** - * Body part of a {@link TableBlock} containing {@link TableRow TableRows}. - */ -public class TableBody extends CustomNode { -} +/** Body part of a {@link TableBlock} containing {@link TableRow TableRows}. */ +public class TableBody extends CustomNode {} diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java index 033c2dd04..d4ad1f171 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java @@ -2,9 +2,7 @@ import org.commonmark.node.CustomNode; -/** - * Table cell of a {@link TableRow} containing inline nodes. - */ +/** Table cell of a {@link TableRow} containing inline nodes. */ public class TableCell extends CustomNode { private boolean header; @@ -34,7 +32,8 @@ public void setAlignment(Alignment alignment) { } /** - * @return the cell width (the number of dash and colon characters in the delimiter row of the table for this column) + * @return the cell width (the number of dash and colon characters in the delimiter row of the + * table for this column) */ public int getWidth() { return width; @@ -44,11 +43,10 @@ public void setWidth(int width) { this.width = width; } - /** - * How the cell is aligned horizontally. - */ + /** How the cell is aligned horizontally. */ public enum Alignment { - LEFT, CENTER, RIGHT + LEFT, + CENTER, + RIGHT } - } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableHead.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableHead.java index 96a95e620..fa771f8f7 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableHead.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableHead.java @@ -2,8 +2,5 @@ import org.commonmark.node.CustomNode; -/** - * Head part of a {@link TableBlock} containing {@link TableRow TableRows}. - */ -public class TableHead extends CustomNode { -} +/** Head part of a {@link TableBlock} containing {@link TableRow TableRows}. */ +public class TableHead extends CustomNode {} diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableRow.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableRow.java index 1325875d0..19b31b9bc 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableRow.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableRow.java @@ -5,5 +5,4 @@ /** * Table row of a {@link TableHead} or {@link TableBody} containing {@link TableCell TableCells}. */ -public class TableRow extends CustomNode { -} +public class TableRow extends CustomNode {} diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java index 4740e9aad..582c30aae 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java @@ -1,5 +1,6 @@ package org.commonmark.ext.gfm.tables; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.ext.gfm.tables.internal.TableBlockParser; import org.commonmark.ext.gfm.tables.internal.TableHtmlNodeRenderer; @@ -13,26 +14,25 @@ import org.commonmark.renderer.markdown.MarkdownRenderer; import org.commonmark.renderer.text.TextContentRenderer; -import java.util.Set; - /** * Extension for GFM tables using "|" pipes (GitHub Flavored Markdown). - *

    - * Create it with {@link #create()} and then configure it on the builders - * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, - * {@link HtmlRenderer.Builder#extensions(Iterable)}). - *

    - *

    - * The parsed tables are turned into {@link TableBlock} blocks. - *

    * - * @see Tables (extension) in GitHub Flavored Markdown Spec + *

    Create it with {@link #create()} and then configure it on the builders ({@link + * org.commonmark.parser.Parser.Builder#extensions(Iterable)}, {@link + * HtmlRenderer.Builder#extensions(Iterable)}). + * + *

    The parsed tables are turned into {@link TableBlock} blocks. + * + * @see Tables (extension) in GitHub + * Flavored Markdown Spec */ -public class TablesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, - TextContentRenderer.TextContentRendererExtension, MarkdownRenderer.MarkdownRendererExtension { +public class TablesExtension + implements Parser.ParserExtension, + HtmlRenderer.HtmlRendererExtension, + TextContentRenderer.TextContentRendererExtension, + MarkdownRenderer.MarkdownRendererExtension { - private TablesExtension() { - } + private TablesExtension() {} public static Extension create() { return new TablesExtension(); @@ -55,16 +55,17 @@ public void extend(TextContentRenderer.Builder rendererBuilder) { @Override public void extend(MarkdownRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { - @Override - public NodeRenderer create(MarkdownNodeRendererContext context) { - return new TableMarkdownNodeRenderer(context); - } + rendererBuilder.nodeRendererFactory( + new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new TableMarkdownNodeRenderer(context); + } - @Override - public Set getSpecialCharacters() { - return Set.of('|'); - } - }); + @Override + public Set getSpecialCharacters() { + return Set.of('|'); + } + }); } } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index 49f17194d..5e74e78eb 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java @@ -1,5 +1,7 @@ package org.commonmark.ext.gfm.tables.internal; +import java.util.ArrayList; +import java.util.List; import org.commonmark.ext.gfm.tables.*; import org.commonmark.node.Block; import org.commonmark.node.Node; @@ -10,9 +12,6 @@ import org.commonmark.parser.block.*; import org.commonmark.text.Characters; -import java.util.ArrayList; -import java.util.List; - public class TableBlockParser extends AbstractBlockParser { private final TableBlock block = new TableBlock(); @@ -42,8 +41,10 @@ public BlockContinue tryContinue(ParserState state) { int pipe = Characters.find('|', content, state.getNextNonSpaceIndex()); if (pipe != -1) { if (pipe == state.getNextNonSpaceIndex()) { - // If we *only* have a pipe character (and whitespace), that is not a valid table row and ends the table. - if (Characters.skipSpaceTab(content, pipe + 1, content.length()) == content.length()) { + // If we *only* have a pipe character (and whitespace), that is not a valid table + // row and ends the table. + if (Characters.skipSpaceTab(content, pipe + 1, content.length()) + == content.length()) { // We also don't want the pipe to be added via lazy continuation. canHaveLazyContinuationLines = false; return BlockContinue.none(); @@ -88,7 +89,8 @@ public void parseInlines(InlineParser inlineParser) { // Body starts at index 2. 0 is header, 1 is separator. for (int rowIndex = 2; rowIndex < rowLines.size(); rowIndex++) { SourceLine rowLine = rowLines.get(rowIndex); - SourceSpan sourceSpan = rowIndex < sourceSpans.size() ? sourceSpans.get(rowIndex) : null; + SourceSpan sourceSpan = + rowIndex < sourceSpans.size() ? sourceSpans.get(rowIndex) : null; List cells = split(rowLine); TableRow row = new TableRow(); if (sourceSpan != null) { @@ -103,7 +105,8 @@ public void parseInlines(InlineParser inlineParser) { } if (body == null) { - // It's valid to have a table without body. In that case, don't add an empty TableBody node. + // It's valid to have a table without body. In that case, don't add an empty + // TableBody node. body = new TableBody(); block.appendChild(body); } @@ -152,9 +155,10 @@ private static List split(SourceLine line) { switch (c) { case '\\': if (i + 1 < cellEnd && row.charAt(i + 1) == '|') { - // Pipe is special for table parsing. An escaped pipe doesn't result in a new cell, but is - // passed down to inline parsing as an unescaped pipe. Note that that applies even for the `\|` - // in an input like `\\|` - in other words, table parsing doesn't support escaping backslashes. + // Pipe is special for table parsing. An escaped pipe doesn't result in a + // new cell, but is passed down to inline parsing as an unescaped pipe. Note + // that that applies even for the `\|` in an input like `\\|` - in other + // words, table parsing doesn't support escaping backslashes. sb.append('|'); i++; } else { @@ -176,7 +180,10 @@ private static List split(SourceLine line) { } if (sb.length() > 0) { String content = sb.toString(); - cells.add(SourceLine.of(content, line.substring(cellStart, line.getContent().length()).getSourceSpan())); + cells.add( + SourceLine.of( + content, + line.substring(cellStart, line.getContent().length()).getSourceSpan())); } return cells; } @@ -211,7 +218,8 @@ private static List parseSeparator(CharSequence s) { case '-': case ':': if (pipes == 0 && !columns.isEmpty()) { - // Need a pipe after the first column (first column doesn't need to start with one) + // Need a pipe after the first column (first column doesn't need to start + // with one) return null; } boolean left = false; @@ -274,9 +282,14 @@ public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { List paragraphLines = matchedBlockParser.getParagraphLines().getLines(); - if (!paragraphLines.isEmpty() && Characters.find('|', paragraphLines.get(paragraphLines.size() - 1).getContent(), 0) != -1) { + if (paragraphLines.isEmpty()) { + return BlockStart.none(); + } + var previousLine = paragraphLines.get(paragraphLines.size() - 1).getContent(); + if (Characters.find('|', previousLine, 0) != -1) { SourceLine line = state.getLine(); - SourceLine separatorLine = line.substring(state.getIndex(), line.getContent().length()); + SourceLine separatorLine = + line.substring(state.getIndex(), line.getContent().length()); List columns = parseSeparator(separatorLine.getContent()); if (columns != null && !columns.isEmpty()) { SourceLine paragraph = paragraphLines.get(paragraphLines.size() - 1); diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java index 966c4c151..81e584c9d 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java @@ -1,12 +1,11 @@ package org.commonmark.ext.gfm.tables.internal; +import java.util.Map; import org.commonmark.ext.gfm.tables.*; import org.commonmark.node.Node; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; -import java.util.Map; - public class TableHtmlNodeRenderer extends TableNodeRenderer { private final HtmlWriter htmlWriter; @@ -69,7 +68,8 @@ private Map getAttributes(Node node, String tagName) { private Map getCellAttributes(TableCell tableCell, String tagName) { if (tableCell.getAlignment() != null) { - return context.extendAttributes(tableCell, tagName, Map.of("align", getAlignValue(tableCell.getAlignment()))); + return context.extendAttributes( + tableCell, tagName, Map.of("align", getAlignValue(tableCell.getAlignment()))); } else { return context.extendAttributes(tableCell, tagName, Map.of()); } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java index b0705f579..a9c4f9049 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableMarkdownNodeRenderer.java @@ -1,16 +1,16 @@ package org.commonmark.ext.gfm.tables.internal; +import java.util.ArrayList; +import java.util.List; import org.commonmark.ext.gfm.tables.*; import org.commonmark.node.Node; import org.commonmark.renderer.markdown.MarkdownNodeRendererContext; import org.commonmark.renderer.markdown.MarkdownWriter; import org.commonmark.text.AsciiMatcher; -import java.util.ArrayList; -import java.util.List; - /** - * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to text content. + * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to + * text content. */ public class TableMarkdownNodeRenderer extends TableNodeRenderer { private final MarkdownWriter writer; diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java index 2982e1518..4e3a00815 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java @@ -1,11 +1,10 @@ package org.commonmark.ext.gfm.tables.internal; +import java.util.Set; import org.commonmark.ext.gfm.tables.*; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; -import java.util.Set; - abstract class TableNodeRenderer implements NodeRenderer { @Override @@ -15,8 +14,7 @@ public Set> getNodeTypes() { TableHead.class, TableBody.class, TableRow.class, - TableCell.class - ); + TableCell.class); } @Override diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java index 0ba6894b5..b9e17c1d3 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java @@ -10,7 +10,8 @@ import org.commonmark.renderer.text.TextContentWriter; /** - * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to text content. + * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to + * text content. */ public class TableTextContentNodeRenderer extends TableNodeRenderer { diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java index 85c11206c..32a8218b2 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TableMarkdownRendererTest.java @@ -1,19 +1,19 @@ package org.commonmark.ext.gfm.tables; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.jupiter.api.Test; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - public class TableMarkdownRendererTest { private static final Set EXTENSIONS = Set.of(TablesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = + MarkdownRenderer.builder().extensions(EXTENSIONS).build(); @Test public void testHeadNoBody() { @@ -60,7 +60,8 @@ public void testEscaping() { @Test public void testEscaped() { - // `|` in Text nodes needs to be escaped, otherwise the generated Markdown does not get parsed back as a table + // `|` in Text nodes needs to be escaped, otherwise the generated Markdown does not get + // parsed back as a table assertRoundTrip("\\|Abc\\|\n\\|---\\|\n"); } diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java index e7f3db4d1..81def7c2e 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java @@ -1,5 +1,7 @@ package org.commonmark.ext.gfm.tables; +import java.util.List; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; @@ -12,19 +14,16 @@ import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.MethodSource; -import java.util.List; -import java.util.Set; - @ParameterizedClass @MethodSource("data") public class TablesSpecTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(TablesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).build(); - @Parameter - Example example; + @Parameter Example example; static List data() { return ExampleReader.readExamples(TestResources.getGfmSpec(), "table"); diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java index 98c3ad4d7..f872c0cde 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -1,5 +1,9 @@ package org.commonmark.ext.gfm.tables; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.node.*; import org.commonmark.parser.IncludeSourceSpans; @@ -9,16 +13,12 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - public class TablesTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(TablesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).build(); @Test public void mustHaveHeaderAndSeparator() { @@ -28,22 +28,26 @@ public void mustHaveHeaderAndSeparator() { @Test public void separatorMustBeOneOrMore() { - assertRendering("Abc|Def\n-|-", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    \n"); - assertRendering("Abc|Def\n--|--", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    \n"); + assertRendering( + "Abc|Def\n-|-", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    \n"); + assertRendering( + "Abc|Def\n--|--", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    \n"); } @Test @@ -55,14 +59,16 @@ public void separatorMustNotContainInvalidChars() { @Test public void separatorCanHaveLeadingSpaceThenPipe() { - assertRendering("Abc|Def\n |---|---", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    \n"); + assertRendering( + "Abc|Def\n |---|---", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    \n"); } @Test @@ -77,25 +83,28 @@ public void separatorNeedsPipes() { @Test public void oneHeadNoBody() { - assertRendering("Abc|Def\n---|---", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    \n"); + assertRendering( + "Abc|Def\n---|---", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    \n"); } @Test public void oneColumnOneHeadNoBody() { - String expected = "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    Abc
    \n"; + String expected = + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    Abc
    \n"; assertRendering("|Abc\n|---\n", expected); assertRendering("|Abc|\n|---|\n", expected); assertRendering("Abc|\n---|\n", expected); @@ -108,18 +117,19 @@ public void oneColumnOneHeadNoBody() { @Test public void oneColumnOneHeadOneBody() { - String expected = "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    Abc
    1
    \n"; + String expected = + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    Abc
    1
    \n"; assertRendering("|Abc\n|---\n|1", expected); assertRendering("|Abc|\n|---|\n|1|", expected); assertRendering("Abc|\n---|\n1|", expected); @@ -130,38 +140,42 @@ public void oneColumnOneHeadOneBody() { @Test public void oneHeadOneBody() { - assertRendering("Abc|Def\n---|---\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n---|---\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void spaceBeforeSeparator() { - assertRendering(" |Abc|Def|\n |---|---|\n |1|2|", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + " |Abc|Def|\n |---|---|\n |1|2|", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test @@ -171,368 +185,408 @@ public void separatorMustNotHaveLessPartsThanHead() { @Test public void padding() { - assertRendering(" Abc | Def \n --- | --- \n 1 | 2 ", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + " Abc | Def \n --- | --- \n 1 | 2 ", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void paddingWithCodeBlockIndentation() { - assertRendering("Abc|Def\n---|---\n 1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n---|---\n 1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void pipesOnOutside() { - assertRendering("|Abc|Def|\n|---|---|\n|1|2|", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + "|Abc|Def|\n|---|---|\n|1|2|", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void pipesOnOutsideWhitespaceAfterHeader() { - assertRendering("|Abc|Def| \n|---|---|\n|1|2|", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + "|Abc|Def| \n|---|---|\n|1|2|", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void pipesOnOutsideZeroLengthHeaders() { // This is literally what someone has done IRL - it helped to expose // an issue with parsing the last header cell correctly - assertRendering("||center header||\n" + - "-|-------------|-\n" + - "1| 2 |3", - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    center header
    123
    \n"); + assertRendering( + "||center header||\n" + "-|-------------|-\n" + "1| 2 |3", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    center header
    123
    \n"); } @Test public void inlineElements() { - assertRendering("*Abc*|Def\n---|---\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + "*Abc*|Def\n---|---\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void escapedPipe() { - assertRendering("Abc|Def\n---|---\n1\\|2|20", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    1|220
    \n"); + assertRendering( + "Abc|Def\n---|---\n1\\|2|20", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    1|220
    \n"); } @Test public void escapedBackslash() { - // This is a bit weird in the GFM spec IMO. `1\\|2` looks like an escaped backslash, followed by a pipe - // (so two cells). Instead, the `\|` is parsed as an escaped pipe first, so just a single cell. The inline - // parser then gets `1\|2` which renders as `1|2`. - assertRendering("Abc|Def\n---|---\n1\\\\|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    1|2
    \n"); + // This is a bit weird in the GFM spec IMO. `1\\|2` looks like an escaped backslash, + // followed by a pipe (so two cells). Instead, the `\|` is parsed as an escaped pipe first, + // so just a single cell. The inline parser then gets `1\|2` which renders as `1|2`. + assertRendering( + "Abc|Def\n---|---\n1\\\\|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    1|2
    \n"); } @Test public void escapedOther() { - // This is a tricky one. For \`, we don't want to remove the backslash when we parse the table, otherwise - // inline parsing is wrong. So we have to be careful where we do/don't consume the backslash. - assertRendering("Abc|Def\n---|---\n1|\\`not code`", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    1`not code`
    \n"); + // This is a tricky one. For \`, we don't want to remove the backslash when we parse the + // table, otherwise inline parsing is wrong. So we have to be careful where we do/don't + // consume the backslash. + assertRendering( + "Abc|Def\n---|---\n1|\\`not code`", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    1`not code`
    \n"); } @Test public void backslashAtEnd() { - assertRendering("Abc|Def\n---|---\n1|2\\", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12\\
    \n"); + assertRendering( + "Abc|Def\n---|---\n1|2\\", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12\\
    \n"); } @Test public void alignLeft() { - assertRendering("Abc|Def\n:-|-\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); - assertRendering("Abc|Def\n:-|-\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); - assertRendering("Abc|Def\n:---|---\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n:-|-\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n:-|-\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n:---|---\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void alignRight() { - assertRendering("Abc|Def\n-:|-\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); - assertRendering("Abc|Def\n--:|--\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); - assertRendering("Abc|Def\n---:|---\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n-:|-\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n--:|--\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n---:|---\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void alignCenter() { - assertRendering("Abc|Def\n:-:|-\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); - assertRendering("Abc|Def\n:--:|--\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); - assertRendering("Abc|Def\n:---:|---\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n:-:|-\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n:--:|--\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n:---:|---\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void alignCenterSecond() { - assertRendering("Abc|Def\n---|:---:\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n---|:---:\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void alignLeftWithSpaces() { - assertRendering("Abc|Def\n :--- |---\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n :--- |---\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test @@ -545,249 +599,272 @@ public void alignmentMarkerMustBeNextToDashes() { @Test public void bodyCanNotHaveMoreColumnsThanHead() { - assertRendering("Abc|Def\n---|---\n1|2|3", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertRendering( + "Abc|Def\n---|---\n1|2|3", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void bodyWithFewerColumnsThanHeadResultsInEmptyCells() { - assertRendering("Abc|Def|Ghi\n---|---|---\n1|2", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDefGhi
    12
    \n"); + assertRendering( + "Abc|Def|Ghi\n---|---|---\n1|2", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDefGhi
    12
    \n"); } @Test public void insideBlockQuote() { - assertRendering("> Abc|Def\n> ---|---\n> 1|2", "

    \n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n" + - "
    \n"); + assertRendering( + "> Abc|Def\n> ---|---\n> 1|2", + "
    \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n" + + "
    \n"); } @Test public void tableWithLazyContinuationLine() { - assertRendering("Abc|Def\n---|---\n1|2\nlazy", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    lazy
    \n"); + assertRendering( + "Abc|Def\n---|---\n1|2\nlazy", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    lazy
    \n"); } @Test public void issue142() { - assertRendering("||Alveolar|Bilabial\n" + - "|:--|:-:|:-:\n" + - "|**Plosive**|t, d|b\n" + - "|**Tap**|ɾ|", - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AlveolarBilabial
    Plosivet, db
    Tapɾ
    \n"); + assertRendering( + "||Alveolar|Bilabial\n" + + "|:--|:-:|:-:\n" + + "|**Plosive**|t, d|b\n" + + "|**Tap**|ɾ|", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AlveolarBilabial
    Plosivet, db
    Tapɾ
    \n"); } @Test public void danglingPipe() { - assertRendering("Abc|Def\n" + - "---|---\n" + - "1|2\n" + - "|", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n" + - "

    |

    \n"); - - assertRendering("Abc|Def\n" + - "---|---\n" + - "1|2\n" + - " | ", "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n" + - "

    |

    \n"); + assertRendering( + "Abc|Def\n" + "---|---\n" + "1|2\n" + "|", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n" + + "

    |

    \n"); + + assertRendering( + "Abc|Def\n" + "---|---\n" + "1|2\n" + " | ", + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n" + + "

    |

    \n"); } @Test public void interruptsParagraph() { - assertRendering("text\n" + - "|a |\n" + - "|---|\n" + - "|b |", "

    text

    \n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    a
    b
    \n"); + assertRendering( + "text\n" + "|a |\n" + "|---|\n" + "|b |", + "

    text

    \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    a
    b
    \n"); } @Test public void attributeProviderIsApplied() { - AttributeProviderFactory factory = context -> (node, tagName, attributes) -> { - if (node instanceof TableBlock) { - attributes.put("test", "block"); - } else if (node instanceof TableHead) { - attributes.put("test", "head"); - } else if (node instanceof TableBody) { - attributes.put("test", "body"); - } else if (node instanceof TableRow) { - attributes.put("test", "row"); - } else if (node instanceof TableCell) { - attributes.put("test", "cell"); - } - }; - HtmlRenderer renderer = HtmlRenderer.builder() - .attributeProviderFactory(factory) - .extensions(EXTENSIONS) - .build(); + AttributeProviderFactory factory = + context -> + (node, tagName, attributes) -> { + if (node instanceof TableBlock) { + attributes.put("test", "block"); + } else if (node instanceof TableHead) { + attributes.put("test", "head"); + } else if (node instanceof TableBody) { + attributes.put("test", "body"); + } else if (node instanceof TableRow) { + attributes.put("test", "row"); + } else if (node instanceof TableCell) { + attributes.put("test", "cell"); + } + }; + HtmlRenderer renderer = + HtmlRenderer.builder() + .attributeProviderFactory(factory) + .extensions(EXTENSIONS) + .build(); String rendered = renderer.render(PARSER.parse("Abc|Def\n---|---\n1|2")); - assertThat(rendered).isEqualTo("\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertThat(rendered) + .isEqualTo( + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void columnWidthIsRecorded() { - AttributeProviderFactory factory = context -> (node, tagName, attributes) -> { - if (node instanceof TableCell && "th".equals(tagName)) { - attributes.put("width", ((TableCell) node).getWidth() + "em"); - } - }; - HtmlRenderer renderer = HtmlRenderer.builder() - .attributeProviderFactory(factory) - .extensions(EXTENSIONS) - .build(); + AttributeProviderFactory factory = + context -> + (node, tagName, attributes) -> { + if (node instanceof TableCell && "th".equals(tagName)) { + attributes.put("width", ((TableCell) node).getWidth() + "em"); + } + }; + HtmlRenderer renderer = + HtmlRenderer.builder() + .attributeProviderFactory(factory) + .extensions(EXTENSIONS) + .build(); String rendered = renderer.render(PARSER.parse("Abc|Def\n-----|---\n1|2")); - assertThat(rendered).isEqualTo("\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
    AbcDef
    12
    \n"); + assertThat(rendered) + .isEqualTo( + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); } @Test public void sourceSpans() { - Parser parser = Parser.builder() - .extensions(EXTENSIONS) - .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) - .build(); + Parser parser = + Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); Node document = parser.parse("Abc|Def\n---|---\n|1|2\n 3|four|\n|||\n"); TableBlock block = (TableBlock) document.getFirstChild(); - assertThat(block.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 7), - SourceSpan.of(2, 0, 16, 4), SourceSpan.of(3, 0, 21, 8), SourceSpan.of(4, 0, 30, 3))); + assertThat(block.getSourceSpans()) + .isEqualTo( + List.of( + SourceSpan.of(0, 0, 0, 7), + SourceSpan.of(1, 0, 8, 7), + SourceSpan.of(2, 0, 16, 4), + SourceSpan.of(3, 0, 21, 8), + SourceSpan.of(4, 0, 30, 3))); TableHead head = (TableHead) block.getFirstChild(); assertThat(head.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 7))); @@ -797,30 +874,41 @@ public void sourceSpans() { TableCell headRowCell1 = (TableCell) headRow.getFirstChild(); TableCell headRowCell2 = (TableCell) headRow.getLastChild(); assertThat(headRowCell1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 3))); - assertThat(headRowCell1.getFirstChild().getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 3))); + assertThat(headRowCell1.getFirstChild().getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 0, 0, 3))); assertThat(headRowCell2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 4, 4, 3))); - assertThat(headRowCell2.getFirstChild().getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 4, 4, 3))); + assertThat(headRowCell2.getFirstChild().getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 4, 4, 3))); TableBody body = (TableBody) block.getLastChild(); - assertThat(body.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 0, 16, 4), SourceSpan.of(3, 0, 21, 8), SourceSpan.of(4, 0, 30, 3))); + assertThat(body.getSourceSpans()) + .isEqualTo( + List.of( + SourceSpan.of(2, 0, 16, 4), + SourceSpan.of(3, 0, 21, 8), + SourceSpan.of(4, 0, 30, 3))); TableRow bodyRow1 = (TableRow) body.getFirstChild(); assertThat(bodyRow1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 0, 16, 4))); TableCell bodyRow1Cell1 = (TableCell) bodyRow1.getFirstChild(); TableCell bodyRow1Cell2 = (TableCell) bodyRow1.getLastChild(); assertThat(bodyRow1Cell1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 1, 17, 1))); - assertThat(bodyRow1Cell1.getFirstChild().getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 1, 17, 1))); + assertThat(bodyRow1Cell1.getFirstChild().getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(2, 1, 17, 1))); assertThat(bodyRow1Cell2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 3, 19, 1))); - assertThat(bodyRow1Cell2.getFirstChild().getSourceSpans()).isEqualTo(List.of(SourceSpan.of(2, 3, 19, 1))); + assertThat(bodyRow1Cell2.getFirstChild().getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(2, 3, 19, 1))); TableRow bodyRow2 = (TableRow) body.getFirstChild().getNext(); assertThat(bodyRow2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 0, 21, 8))); TableCell bodyRow2Cell1 = (TableCell) bodyRow2.getFirstChild(); TableCell bodyRow2Cell2 = (TableCell) bodyRow2.getLastChild(); assertThat(bodyRow2Cell1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 1, 22, 1))); - assertThat(bodyRow2Cell1.getFirstChild().getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 1, 22, 1))); + assertThat(bodyRow2Cell1.getFirstChild().getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(3, 1, 22, 1))); assertThat(bodyRow2Cell2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 3, 24, 4))); - assertThat(bodyRow2Cell2.getFirstChild().getSourceSpans()).isEqualTo(List.of(SourceSpan.of(3, 3, 24, 4))); + assertThat(bodyRow2Cell2.getFirstChild().getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(3, 3, 24, 4))); TableRow bodyRow3 = (TableRow) body.getLastChild(); assertThat(bodyRow3.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(4, 0, 30, 3))); @@ -832,15 +920,12 @@ public void sourceSpans() { @Test public void sourceSpansWhenInterrupting() { - var parser = Parser.builder() - .extensions(EXTENSIONS) - .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) - .build(); - var document = parser.parse("a\n" + - "bc\n" + - "|de|\n" + - "|---|\n" + - "|fg|"); + var parser = + Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); + var document = parser.parse("a\n" + "bc\n" + "|de|\n" + "|---|\n" + "|fg|"); var paragraph = (Paragraph) document.getFirstChild(); var text = (Text) paragraph.getFirstChild(); @@ -849,15 +934,16 @@ public void sourceSpansWhenInterrupting() { var text2 = (Text) text.getNext().getNext(); assertThat(text2.getLiteral()).isEqualTo("bc"); - assertThat(paragraph.getSourceSpans()).isEqualTo(List.of( - SourceSpan.of(0, 0, 0, 1), - SourceSpan.of(1, 0, 2, 2))); + assertThat(paragraph.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 0, 0, 1), SourceSpan.of(1, 0, 2, 2))); var table = (TableBlock) document.getLastChild(); - assertThat(table.getSourceSpans()).isEqualTo(List.of( - SourceSpan.of(2, 0, 5, 4), - SourceSpan.of(3, 0, 10, 5), - SourceSpan.of(4, 0, 16, 4))); + assertThat(table.getSourceSpans()) + .isEqualTo( + List.of( + SourceSpan.of(2, 0, 5, 4), + SourceSpan.of(3, 0, 10, 5), + SourceSpan.of(4, 0, 16, 4))); } @Override diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java index 966f097fd..a57ce9eac 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java @@ -1,5 +1,6 @@ package org.commonmark.ext.gfm.tables; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.text.LineBreakRendering; @@ -7,19 +8,25 @@ import org.commonmark.testutil.Asserts; import org.junit.jupiter.api.Test; -import java.util.Set; - public class TablesTextContentTest { private static final Set EXTENSIONS = Set.of(TablesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final TextContentRenderer RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS).build(); - - private static final TextContentRenderer COMPACT_RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS).build(); - private static final TextContentRenderer SEPARATE_RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS) - .lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS).build(); - private static final TextContentRenderer STRIPPED_RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS) - .lineBreakRendering(LineBreakRendering.STRIP).build(); + private static final TextContentRenderer RENDERER = + TextContentRenderer.builder().extensions(EXTENSIONS).build(); + + private static final TextContentRenderer COMPACT_RENDERER = + TextContentRenderer.builder().extensions(EXTENSIONS).build(); + private static final TextContentRenderer SEPARATE_RENDERER = + TextContentRenderer.builder() + .extensions(EXTENSIONS) + .lineBreakRendering(LineBreakRendering.SEPARATE_BLOCKS) + .build(); + private static final TextContentRenderer STRIPPED_RENDERER = + TextContentRenderer.builder() + .extensions(EXTENSIONS) + .lineBreakRendering(LineBreakRendering.STRIP) + .build(); @Test public void oneHeadNoBody() { diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java index 5921f3c5b..1b9f5487e 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java @@ -6,17 +6,20 @@ /** * Extension for adding auto generated IDs to headings. - *

    - * Create it with {@link #create()} or {@link #builder()} and then configure it on the - * renderer builder ({@link HtmlRenderer.Builder#extensions(Iterable)}). - *

    - * The heading text will be used to create the id. Multiple headings with the - * same text will result in appending a hyphen and number. For example: + * + *

    Create it with {@link #create()} or {@link #builder()} and then configure it on the renderer + * builder ({@link HtmlRenderer.Builder#extensions(Iterable)}). + * + *

    The heading text will be used to create the id. Multiple headings with the same text will + * result in appending a hyphen and number. For example: + * *

    
      * # Heading
      * # Heading
      * 
    + * * will result in + * *
    
      * <h1 id="heading">Heading</h1>
      * <h1 id="heading-1">Heading</h1>
    @@ -52,7 +55,8 @@ public static Builder builder() {
     
         @Override
         public void extend(HtmlRenderer.Builder rendererBuilder) {
    -        rendererBuilder.attributeProviderFactory(context -> HeadingIdAttributeProvider.create(defaultId, idPrefix, idSuffix));
    +        rendererBuilder.attributeProviderFactory(
    +                context -> HeadingIdAttributeProvider.create(defaultId, idPrefix, idSuffix));
         }
     
         public static class Builder {
    @@ -61,7 +65,8 @@ public static class Builder {
             private String idSuffix = "";
     
             /**
    -         * @param value Default value for the id to take if no generated id can be extracted. Default "id"
    +         * @param value Default value for the id to take if no generated id can be extracted.
    +         *     Default "id"
              * @return {@code this}
              */
             public Builder defaultId(String value) {
    diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java
    index 2c37433e2..8ab5b3b32 100644
    --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java
    +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java
    @@ -7,8 +7,8 @@
     
     /**
      * Generates strings to be used as identifiers.
    - * 

    - * Use {@link #builder()} to create an instance. + * + *

    Use {@link #builder()} to create an instance. */ public class IdGenerator { private final Pattern allowedCharacters; @@ -33,35 +33,35 @@ public static Builder builder() { } /** - *

    * Generate an ID based on the provided text and previously generated IDs. - *

    - * This method is not thread safe, concurrent calls can end up - * with non-unique identifiers. - *

    - * Note that collision can occur in the case that + * + *

    This method is not thread safe, concurrent calls can end up with non-unique identifiers. + * + *

    Note that collision can occur in the case that + * *

      - *
    • Method called with 'X'
    • - *
    • Method called with 'X' again
    • - *
    • Method called with 'X-1'
    • + *
    • Method called with 'X' + *
    • Method called with 'X' again + *
    • Method called with 'X-1' *
    - *

    - * In that case, the three generated IDs will be: + * + *

    In that case, the three generated IDs will be: + * *

      - *
    • X
    • - *
    • X-1
    • - *
    • X-1
    • + *
    • X + *
    • X-1 + *
    • X-1 *
    - *

    - * Therefore if collisions are unacceptable you should ensure that - * numbers are stripped from end of {@code text}. * - * @param text Text that the identifier should be based on. Will be normalised, then used to generate the - * identifier. - * @return {@code text} if this is the first instance that the {@code text} has been passed - * to the method. Otherwise, {@code text + "-" + X} will be returned, where X is the number of times - * that {@code text} has previously been passed in. If {@code text} is empty, the default - * identifier given in the constructor will be used. + *

    Therefore if collisions are unacceptable you should ensure that numbers are stripped from + * end of {@code text}. + * + * @param text Text that the identifier should be based on. Will be normalised, then used to + * generate the identifier. + * @return {@code text} if this is the first instance that the {@code text} has been passed to + * the method. Otherwise, {@code text + "-" + X} will be returned, where X is the number of + * times that {@code text} has previously been passed in. If {@code text} is empty, the + * default identifier given in the constructor will be used. */ public String generateId(String text) { String normalizedIdentity = text != null ? normalizeText(text) : defaultIdentifier; @@ -85,7 +85,8 @@ private static Pattern compileAllowedCharactersPattern() { try { return Pattern.compile(regex, Pattern.UNICODE_CHARACTER_CLASS); } catch (IllegalArgumentException e) { - // Android only supports the flag in API level 24. But it actually uses Unicode character classes by + // Android only supports the flag in API level 24. But it actually uses Unicode + // character classes by // default, so not specifying the flag is ok. See issue #71. return Pattern.compile(regex); } @@ -119,7 +120,8 @@ public IdGenerator build() { } /** - * @param defaultId the default identifier to use in case the provided text is empty or only contains unusable characters + * @param defaultId the default identifier to use in case the provided text is empty or only + * contains unusable characters * @return {@code this} */ public Builder defaultId(String defaultId) { diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java index 6b8792bd5..f7d1b75a1 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java @@ -1,26 +1,23 @@ package org.commonmark.ext.heading.anchor.internal; -import org.commonmark.ext.heading.anchor.IdGenerator; -import org.commonmark.renderer.html.AttributeProvider; -import org.commonmark.node.*; - import java.util.ArrayList; import java.util.List; import java.util.Map; +import org.commonmark.ext.heading.anchor.IdGenerator; +import org.commonmark.node.*; +import org.commonmark.renderer.html.AttributeProvider; public class HeadingIdAttributeProvider implements AttributeProvider { private final IdGenerator idGenerator; private HeadingIdAttributeProvider(String defaultId, String prefix, String suffix) { - idGenerator = IdGenerator.builder() - .defaultId(defaultId) - .prefix(prefix) - .suffix(suffix) - .build(); + idGenerator = + IdGenerator.builder().defaultId(defaultId).prefix(prefix).suffix(suffix).build(); } - public static HeadingIdAttributeProvider create(String defaultId, String prefix, String suffix) { + public static HeadingIdAttributeProvider create( + String defaultId, String prefix, String suffix) { return new HeadingIdAttributeProvider(defaultId, prefix, suffix); } @@ -31,17 +28,18 @@ public void setAttributes(Node node, String tagName, final Map a final List wordList = new ArrayList<>(); - node.accept(new AbstractVisitor() { - @Override - public void visit(Text text) { - wordList.add(text.getLiteral()); - } - - @Override - public void visit(Code code) { - wordList.add(code.getLiteral()); - } - }); + node.accept( + new AbstractVisitor() { + @Override + public void visit(Text text) { + wordList.add(text.getLiteral()); + } + + @Override + public void visit(Code code) { + wordList.add(code.getLiteral()); + } + }); String finalString = ""; for (String word : wordList) { diff --git a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java index 438a3a9bd..c78b7c61a 100644 --- a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java @@ -1,34 +1,31 @@ package org.commonmark.ext.heading.anchor; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.junit.jupiter.api.Test; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - public class HeadingAnchorConfigurationTest { private static final Parser PARSER = Parser.builder().build(); private HtmlRenderer buildRenderer(String defaultId, String prefix, String suffix) { - Extension ext = HeadingAnchorExtension.builder() - .defaultId(defaultId) - .idPrefix(prefix) - .idSuffix(suffix) - .build(); - return HtmlRenderer.builder() - .extensions(List.of(ext)) - .build(); + Extension ext = + HeadingAnchorExtension.builder() + .defaultId(defaultId) + .idPrefix(prefix) + .idSuffix(suffix) + .build(); + return HtmlRenderer.builder().extensions(List.of(ext)).build(); } @Test public void testDefaultConfigurationHasNoAdditions() { - HtmlRenderer renderer = HtmlRenderer.builder() - .extensions(List.of(HeadingAnchorExtension.create())) - .build(); + HtmlRenderer renderer = + HtmlRenderer.builder().extensions(List.of(HeadingAnchorExtension.create())).build(); assertThat(doRender(renderer, "# ")).isEqualTo("

    \n"); } @@ -53,5 +50,4 @@ public void testSuffixAddedToHeader() { private String doRender(HtmlRenderer renderer, String text) { return renderer.render(PARSER.parse(text)); } - } diff --git a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java index 3149542e3..95ee2d7eb 100644 --- a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java @@ -1,34 +1,35 @@ package org.commonmark.ext.heading.anchor; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import java.util.Set; - public class HeadingAnchorTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(HeadingAnchorExtension.create()); private static final Parser PARSER = Parser.builder().build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).build(); @Test public void baseCaseSingleHeader() { - assertRendering("# Heading here\n", - "

    Heading here

    \n"); + assertRendering("# Heading here\n", "

    Heading here

    \n"); } @Test public void singleHeaderWithCodeBlock() { - assertRendering("Hi there\n# Heading `here`\n", + assertRendering( + "Hi there\n# Heading `here`\n", "

    Hi there

    \n

    Heading here

    \n"); } @Test public void duplicateHeadersMakeUniqueIds() { - assertRendering("# Heading here\n# Heading here", + assertRendering( + "# Heading here\n# Heading here", "

    Heading here

    \n

    Heading here

    \n"); } @@ -44,34 +45,40 @@ public void testUndertieUnicodeDisplayed() { @Test public void testExplicitHeaderCollision() { - assertRendering("# Header\n# Header\n# Header-1", - "

    Header

    \n" + - "

    Header

    \n" + - "

    Header-1

    \n"); + assertRendering( + "# Header\n# Header\n# Header-1", + "

    Header

    \n" + + "

    Header

    \n" + + "

    Header-1

    \n"); } @Test public void testCaseIsIgnoredWhenComparingIds() { - assertRendering("# HEADING here\n" + - "# heading here", - "

    HEADING here

    \n" + - "

    heading here

    \n"); + assertRendering( + "# HEADING here\n" + "# heading here", + "

    HEADING here

    \n" + + "

    heading here

    \n"); } @Test public void testNestedBlocks() { - assertRendering("## `h` `e` **l** *l* o", + assertRendering( + "## `h` `e` **l** *l* o", "

    h e l l o

    \n"); } @Test public void boldEmphasisCharacters() { - assertRendering("# _hello_ **there**", "

    hello there

    \n"); + assertRendering( + "# _hello_ **there**", + "

    hello there

    \n"); } @Test public void testStrongEmphasis() { - assertRendering("# _**Hi there**_", "

    Hi there

    \n"); + assertRendering( + "# _**Hi there**_", + "

    Hi there

    \n"); } @Test @@ -86,7 +93,9 @@ public void testNonAsciiCharacterHeading() { @Test public void testCombiningDiaeresis() { - assertRendering("# Product\u036D\u036B", "

    Product\u036D\u036B

    \n"); + assertRendering( + "# Product\u036D\u036B", + "

    Product\u036D\u036B

    \n"); } @Override diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributes.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributes.java index 1ee43958b..1739aae77 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributes.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributes.java @@ -1,13 +1,10 @@ package org.commonmark.ext.image.attributes; +import java.util.Map; import org.commonmark.node.CustomNode; import org.commonmark.node.Delimited; -import java.util.Map; - -/** - * A node containing text and other inline nodes as children. - */ +/** A node containing text and other inline nodes as children. */ public class ImageAttributes extends CustomNode implements Delimited { private final Map attributes; diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java index 9a4de2628..f6becc651 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java @@ -8,18 +8,17 @@ /** * Extension for adding attributes to image nodes. - *

    - * Create it with {@link #create()} and then configure it on the builders - * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, - * {@link HtmlRenderer.Builder#extensions(Iterable)}). - *

    + * + *

    Create it with {@link #create()} and then configure it on the builders ({@link + * org.commonmark.parser.Parser.Builder#extensions(Iterable)}, {@link + * HtmlRenderer.Builder#extensions(Iterable)}). * * @since 0.15.0 */ -public class ImageAttributesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { +public class ImageAttributesExtension + implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { - private ImageAttributesExtension() { - } + private ImageAttributesExtension() {} public static Extension create() { return new ImageAttributesExtension(); @@ -32,6 +31,7 @@ public void extend(Parser.Builder parserBuilder) { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.attributeProviderFactory(context -> ImageAttributesAttributeProvider.create()); + rendererBuilder.attributeProviderFactory( + context -> ImageAttributesAttributeProvider.create()); } } diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java index bd89738de..c132c9dcd 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java @@ -1,5 +1,6 @@ package org.commonmark.ext.image.attributes.internal; +import java.util.*; import org.commonmark.ext.image.attributes.ImageAttributes; import org.commonmark.node.AbstractVisitor; import org.commonmark.node.CustomNode; @@ -7,12 +8,9 @@ import org.commonmark.node.Node; import org.commonmark.renderer.html.AttributeProvider; -import java.util.*; - public class ImageAttributesAttributeProvider implements AttributeProvider { - private ImageAttributesAttributeProvider() { - } + private ImageAttributesAttributeProvider() {} public static ImageAttributesAttributeProvider create() { return new ImageAttributesAttributeProvider(); @@ -21,17 +19,18 @@ public static ImageAttributesAttributeProvider create() { @Override public void setAttributes(Node node, String tagName, final Map attributes) { if (node instanceof Image) { - node.accept(new AbstractVisitor() { - @Override - public void visit(CustomNode node) { - if (node instanceof ImageAttributes) { - ImageAttributes imageAttributes = (ImageAttributes) node; - attributes.putAll(imageAttributes.getAttributes()); - // Now that we have used the image attributes we remove the node. - imageAttributes.unlink(); - } - } - }); + node.accept( + new AbstractVisitor() { + @Override + public void visit(CustomNode node) { + if (node instanceof ImageAttributes) { + ImageAttributes imageAttributes = (ImageAttributes) node; + attributes.putAll(imageAttributes.getAttributes()); + // Now that we have used the image attributes we remove the node. + imageAttributes.unlink(); + } + } + }); } } } diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java index 06a39ba23..53ba882c9 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java @@ -1,5 +1,6 @@ package org.commonmark.ext.image.attributes.internal; +import java.util.*; import org.commonmark.ext.image.attributes.ImageAttributes; import org.commonmark.node.Image; import org.commonmark.node.Node; @@ -8,8 +9,6 @@ import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.delimiter.DelimiterRun; -import java.util.*; - public class ImageAttributesDelimiterProcessor implements DelimiterProcessor { // Only allow a defined set of attributes to be used. @@ -36,8 +35,8 @@ public int process(DelimiterRun openingRun, DelimiterRun closingRun) { return 0; } - // Check if the attributes can be applied - if the previous node is an Image, and if all the attributes are in - // the set of SUPPORTED_ATTRIBUTES + // Check if the attributes can be applied - if the previous node is an Image, and if all the + // attributes are in the set of SUPPORTED_ATTRIBUTES Text opener = openingRun.getOpener(); Node nodeToStyle = opener.getPrevious(); if (!(nodeToStyle instanceof Image)) { @@ -53,7 +52,8 @@ public int process(DelimiterRun openingRun, DelimiterRun closingRun) { content.append(((Text) node).getLiteral()); toUnlink.add(node); } else { - // This node type is not supported, so stop here (no need to check any further ones). + // This node type is not supported, so stop here (no need to check any further + // ones). return 0; } } @@ -65,7 +65,8 @@ public int process(DelimiterRun openingRun, DelimiterRun closingRun) { if (attribute.length > 1 && SUPPORTED_ATTRIBUTES.contains(attribute[0].toLowerCase())) { attributesMap.put(attribute[0], attribute[1]); } else { - // This attribute is not supported, so stop here (no need to check any further ones). + // This attribute is not supported, so stop here (no need to check any further + // ones). return 0; } } diff --git a/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java b/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java index 3edf8497e..4232646a2 100644 --- a/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java +++ b/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java @@ -1,5 +1,9 @@ package org.commonmark.ext.image.attributes; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.node.Node; import org.commonmark.node.Paragraph; @@ -10,44 +14,48 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - public class ImageAttributesTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(ImageAttributesExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).build(); @Test public void baseCase() { - assertRendering("![text](/url.png){height=5}", + assertRendering( + "![text](/url.png){height=5}", "

    \"text\"

    \n"); - assertRendering("![text](/url.png){height=5 width=6}", + assertRendering( + "![text](/url.png){height=5 width=6}", "

    \"text\"

    \n"); - assertRendering("![text](/url.png){height=99px width=100px}", + assertRendering( + "![text](/url.png){height=99px width=100px}", "

    \"text\"

    \n"); - assertRendering("![text](/url.png){width=100 height=100}", + assertRendering( + "![text](/url.png){width=100 height=100}", "

    \"text\"

    \n"); - assertRendering("![text](/url.png){height=4.8 width=3.14}", + assertRendering( + "![text](/url.png){height=4.8 width=3.14}", "

    \"text\"

    \n"); - assertRendering("![text](/url.png){Width=18 HeIgHt=1001}", + assertRendering( + "![text](/url.png){Width=18 HeIgHt=1001}", "

    \"text\"

    \n"); - assertRendering("![text](/url.png){height=green width=blue}", + assertRendering( + "![text](/url.png){height=green width=blue}", "

    \"text\"

    \n"); } @Test public void doubleDelimiters() { - assertRendering("![text](/url.png){{height=5}}", + assertRendering( + "![text](/url.png){{height=5}}", "

    \"text\"{{height=5}}

    \n"); } @@ -58,55 +66,66 @@ public void mismatchingDelimitersAreIgnored() { @Test public void unsupportedStyleNamesAreLeftUnchanged() { - assertRendering("![text](/url.png){j=502 K=101 img=2 url=5}", + assertRendering( + "![text](/url.png){j=502 K=101 img=2 url=5}", "

    \"text\"{j=502 K=101 img=2 url=5}

    \n"); - assertRendering("![foo](/url.png){height=3 invalid}\n", + assertRendering( + "![foo](/url.png){height=3 invalid}\n", "

    \"foo\"{height=3 invalid}

    \n"); - assertRendering("![foo](/url.png){height=3 *test*}\n", + assertRendering( + "![foo](/url.png){height=3 *test*}\n", "

    \"foo\"{height=3 test}

    \n"); } @Test public void styleWithNoValueIsIgnored() { - assertRendering("![text](/url.png){height}", + assertRendering( + "![text](/url.png){height}", "

    \"text\"{height}

    \n"); } @Test public void repeatedStyleNameUsesFinalOne() { - assertRendering("![text](/url.png){height=4 height=5 width=1 height=6}", + assertRendering( + "![text](/url.png){height=4 height=5 width=1 height=6}", "

    \"text\"

    \n"); } @Test public void styleValuesAreEscaped() { - assertRendering("![text](/url.png){height=\"text\"

    \n"); - assertRendering("![text](/url.png){height=\"\"img}", + assertRendering( + "![text](/url.png){height=\"\"img}", "

    \"text\"

    \n"); } @Test public void imageAltTextWithSpaces() { - assertRendering("![Android SDK Manager](/contrib/android-sdk-manager.png){height=502 width=101}", + assertRendering( + "![Android SDK Manager](/contrib/android-sdk-manager.png){height=502 width=101}", "

    \"Android

    \n"); } @Test public void imageAltTextWithSoftLineBreak() { - assertRendering("![foo\nbar](/url){height=101 width=202}\n", + assertRendering( + "![foo\nbar](/url){height=101 width=202}\n", "

    \"foo\nbar\"

    \n"); } @Test public void imageAltTextWithHardLineBreak() { - assertRendering("![foo \nbar](/url){height=506 width=1}\n", + assertRendering( + "![foo \nbar](/url){height=506 width=1}\n", "

    \"foo\nbar\"

    \n"); } @Test public void imageAltTextWithEntities() { - assertRendering("![foo ä](/url){height=99 width=100}\n", + assertRendering( + "![foo ä](/url){height=99 width=100}\n", "

    \"foo

    \n"); } @@ -114,20 +133,24 @@ public void imageAltTextWithEntities() { public void textNodesAreUnchanged() { assertRendering("x{height=3 width=4}\n", "

    x{height=3 width=4}

    \n"); assertRendering("x {height=3 width=4}\n", "

    x {height=3 width=4}

    \n"); - assertRendering("\\documentclass[12pt]{article}\n", "

    \\documentclass[12pt]{article}

    \n"); - assertRendering("some *text*{height=3 width=4}\n", "

    some text{height=3 width=4}

    \n"); + assertRendering( + "\\documentclass[12pt]{article}\n", "

    \\documentclass[12pt]{article}

    \n"); + assertRendering( + "some *text*{height=3 width=4}\n", "

    some text{height=3 width=4}

    \n"); assertRendering("{NN} text", "

    {NN} text

    \n"); assertRendering("{}", "

    {}

    \n"); } @Test public void sourceSpans() { - Parser parser = Parser.builder() - .extensions(EXTENSIONS) - .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) - .build(); - - // This doesn't result in image attributes, so source spans should be for the single (merged) text node. + Parser parser = + Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); + + // This doesn't result in image attributes, so source spans should be for the single + // (merged) text node. Node document = parser.parse("x{height=3 width=4}\n"); Paragraph block = (Paragraph) document.getFirstChild(); Node text = block.getFirstChild(); diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/Ins.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/Ins.java index 2ebd4f5ca..b6d7a2f2a 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/Ins.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/Ins.java @@ -3,9 +3,7 @@ import org.commonmark.node.CustomNode; import org.commonmark.node.Delimited; -/** - * An ins node containing text and other inline nodes as children. - */ +/** An ins node containing text and other inline nodes as children. */ public class Ins extends CustomNode implements Delimited { private static final String DELIMITER = "++"; diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java index 3d2974916..a39b77af8 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java @@ -1,5 +1,6 @@ package org.commonmark.ext.ins; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.ext.ins.internal.InsDelimiterProcessor; import org.commonmark.ext.ins.internal.InsHtmlNodeRenderer; @@ -13,23 +14,22 @@ import org.commonmark.renderer.markdown.MarkdownRenderer; import org.commonmark.renderer.text.TextContentRenderer; -import java.util.Set; - /** * Extension for ins using ++ - *

    - * Create it with {@link #create()} and then configure it on the builders - * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, - * {@link HtmlRenderer.Builder#extensions(Iterable)}). - *

    - *

    - * The parsed ins text regions are turned into {@link Ins} nodes. - *

    + * + *

    Create it with {@link #create()} and then configure it on the builders ({@link + * org.commonmark.parser.Parser.Builder#extensions(Iterable)}, {@link + * HtmlRenderer.Builder#extensions(Iterable)}). + * + *

    The parsed ins text regions are turned into {@link Ins} nodes. */ -public class InsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, TextContentRenderer.TextContentRendererExtension, MarkdownRenderer.MarkdownRendererExtension { +public class InsExtension + implements Parser.ParserExtension, + HtmlRenderer.HtmlRendererExtension, + TextContentRenderer.TextContentRendererExtension, + MarkdownRenderer.MarkdownRendererExtension { - private InsExtension() { - } + private InsExtension() {} public static Extension create() { return new InsExtension(); @@ -52,18 +52,20 @@ public void extend(TextContentRenderer.Builder rendererBuilder) { @Override public void extend(MarkdownRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { - @Override - public NodeRenderer create(MarkdownNodeRendererContext context) { - return new InsMarkdownNodeRenderer(context); - } + rendererBuilder.nodeRendererFactory( + new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new InsMarkdownNodeRenderer(context); + } - @Override - public Set getSpecialCharacters() { - // We technically don't need to escape single occurrences of +, but that's all the extension API - // exposes currently. - return Set.of('+'); - } - }); + @Override + public Set getSpecialCharacters() { + // We technically don't need to escape single occurrences of +, but that's + // all the extension API + // exposes currently. + return Set.of('+'); + } + }); } } diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsDelimiterProcessor.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsDelimiterProcessor.java index b0bfb4c6e..a742c70cf 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsDelimiterProcessor.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsDelimiterProcessor.java @@ -28,7 +28,8 @@ public int getMinLength() { @Override public int process(DelimiterRun openingRun, DelimiterRun closingRun) { if (openingRun.length() >= 2 && closingRun.length() >= 2) { - // Use exactly two delimiters even if we have more, and don't care about internal openers/closers. + // Use exactly two delimiters even if we have more, and don't care about internal + // openers/closers. Text opener = openingRun.getOpener(); diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsHtmlNodeRenderer.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsHtmlNodeRenderer.java index dcd05fd59..d4defcbdc 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsHtmlNodeRenderer.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsHtmlNodeRenderer.java @@ -1,11 +1,10 @@ package org.commonmark.ext.ins.internal; +import java.util.Map; import org.commonmark.node.Node; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; -import java.util.Map; - public class InsHtmlNodeRenderer extends InsNodeRenderer { private final HtmlNodeRendererContext context; diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java index 31f0a64ec..4e8e0283e 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java @@ -1,11 +1,10 @@ package org.commonmark.ext.ins.internal; +import java.util.Set; import org.commonmark.ext.ins.Ins; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; -import java.util.Set; - abstract class InsNodeRenderer implements NodeRenderer { @Override diff --git a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java index 6fc9ead67..023413121 100644 --- a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java +++ b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsMarkdownRendererTest.java @@ -1,19 +1,19 @@ package org.commonmark.ext.ins; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.jupiter.api.Test; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - public class InsMarkdownRendererTest { private static final Set EXTENSIONS = Set.of(InsExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = + MarkdownRenderer.builder().extensions(EXTENSIONS).build(); @Test public void testStrikethrough() { diff --git a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java index a5c91a395..8687d9a06 100644 --- a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java +++ b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java @@ -1,5 +1,9 @@ package org.commonmark.ext.ins; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.node.Node; import org.commonmark.node.Paragraph; @@ -11,18 +15,14 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - public class InsTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(InsExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); - private static final TextContentRenderer CONTENT_RENDERER = TextContentRenderer.builder() - .extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final TextContentRenderer CONTENT_RENDERER = + TextContentRenderer.builder().extensions(EXTENSIONS).build(); @Test public void onePlusIsNotEnough() { @@ -68,13 +68,15 @@ public void plusesInside() { @Test public void insWholeParagraphWithOtherDelimiters() { - assertRendering("++Paragraph with *emphasis* and __strong emphasis__++", + assertRendering( + "++Paragraph with *emphasis* and __strong emphasis__++", "

    Paragraph with emphasis and strong emphasis

    \n"); } @Test public void insideBlockQuote() { - assertRendering("> underline ++that++", + assertRendering( + "> underline ++that++", "
    \n

    underline that

    \n
    \n"); } @@ -94,10 +96,11 @@ public void textContentRenderer() { @Test public void sourceSpans() { - Parser parser = Parser.builder() - .extensions(EXTENSIONS) - .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) - .build(); + Parser parser = + Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); Node document = parser.parse("hey ++there++\n"); Paragraph block = (Paragraph) document.getFirstChild(); diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemMarker.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemMarker.java index 9eca59bc9..4cfcb73eb 100644 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemMarker.java +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemMarker.java @@ -2,9 +2,7 @@ import org.commonmark.node.CustomNode; -/** - * A marker node indicating that a list item contains a task. - */ +/** A marker node indicating that a list item contains a task. */ public class TaskListItemMarker extends CustomNode { private final boolean checked; diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java index 1c89256d3..abddc519a 100644 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java @@ -14,19 +14,19 @@ /** * Extension for adding task list items. - *

    - * Create it with {@link #create()} and then configure it on the builders - * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, - * {@link HtmlRenderer.Builder#extensions(Iterable)}). - *

    + * + *

    Create it with {@link #create()} and then configure it on the builders ({@link + * org.commonmark.parser.Parser.Builder#extensions(Iterable)}, {@link + * HtmlRenderer.Builder#extensions(Iterable)}). * * @since 0.15.0 */ -public class TaskListItemsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, - MarkdownRenderer.MarkdownRendererExtension { +public class TaskListItemsExtension + implements Parser.ParserExtension, + HtmlRenderer.HtmlRendererExtension, + MarkdownRenderer.MarkdownRendererExtension { - private TaskListItemsExtension() { - } + private TaskListItemsExtension() {} public static Extension create() { return new TaskListItemsExtension(); @@ -44,16 +44,17 @@ public void extend(HtmlRenderer.Builder rendererBuilder) { @Override public void extend(MarkdownRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { - @Override - public NodeRenderer create(MarkdownNodeRendererContext context) { - return new TaskListItemMarkdownNodeRenderer(context); - } - - @Override - public Set getSpecialCharacters() { - return Set.of(); - } - }); + rendererBuilder.nodeRendererFactory( + new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new TaskListItemMarkdownNodeRenderer(context); + } + + @Override + public Set getSpecialCharacters() { + return Set.of(); + } + }); } } diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java index a27b125c8..cb1c275b4 100644 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java @@ -1,13 +1,12 @@ package org.commonmark.ext.task.list.items.internal; +import java.util.LinkedHashMap; +import java.util.Map; import org.commonmark.ext.task.list.items.TaskListItemMarker; import org.commonmark.node.Node; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; -import java.util.LinkedHashMap; -import java.util.Map; - public class TaskListItemHtmlNodeRenderer extends TaskListItemNodeRenderer { private final HtmlNodeRendererContext context; diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemPostProcessor.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemPostProcessor.java index b95c2e30d..2898a8229 100644 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemPostProcessor.java +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemPostProcessor.java @@ -1,12 +1,11 @@ package org.commonmark.ext.task.list.items.internal; -import org.commonmark.ext.task.list.items.TaskListItemMarker; -import org.commonmark.node.*; -import org.commonmark.parser.PostProcessor; - import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.commonmark.ext.task.list.items.TaskListItemMarker; +import org.commonmark.node.*; +import org.commonmark.parser.PostProcessor; public class TaskListItemPostProcessor implements PostProcessor { @@ -31,14 +30,16 @@ public void visit(ListItem listItem) { Matcher matcher = REGEX_TASK_LIST_ITEM.matcher(textNode.getLiteral()); if (matcher.matches()) { String checked = matcher.group(1); - boolean isChecked = Objects.equals(checked, "X") || Objects.equals(checked, "x"); + boolean isChecked = + Objects.equals(checked, "X") || Objects.equals(checked, "x"); // Add the task list item marker node as the first child of the list item. listItem.prependChild(new TaskListItemMarker(isChecked)); - // Parse the node using the input after the task marker (in other words, group 2 from the matcher). - // (Note that the String has been trimmed, so we should add a space between the - // TaskListItemMarker and the text that follows it when we come to render it). + // Parse the node using the input after the task marker (in other words, + // group 2 from the matcher). (Note that the String has been trimmed, so we + // should add a space between the TaskListItemMarker and the text that + // follows it when we come to render it). textNode.setLiteral(matcher.group(2)); } } diff --git a/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemMarkdownRendererTest.java b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemMarkdownRendererTest.java index cf73f434c..e67768f16 100644 --- a/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemMarkdownRendererTest.java +++ b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemMarkdownRendererTest.java @@ -1,5 +1,7 @@ package org.commonmark.ext.task.list.items; +import static org.assertj.core.api.Assertions.assertThat; + import java.util.Set; import org.commonmark.Extension; import org.commonmark.node.BulletList; @@ -12,13 +14,12 @@ import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class TaskListItemMarkdownRendererTest { private static final Set EXTENSIONS = Set.of(TaskListItemsExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = + MarkdownRenderer.builder().extensions(EXTENSIONS).build(); @Test public void testCheckedRoundTrip() { diff --git a/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java index 0adc615a7..dd9290b6e 100644 --- a/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java +++ b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java @@ -1,87 +1,116 @@ package org.commonmark.ext.task.list.items; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import java.util.Set; - public class TaskListItemsTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(TaskListItemsExtension.create()); - private static final String HTML_CHECKED = ""; + private static final String HTML_CHECKED = + ""; private static final String HTML_UNCHECKED = ""; private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).build(); @Test public void baseCase() { - assertRendering("- [x] this is *done*\n", "

      \n
    • " + HTML_CHECKED + " this is done
    • \n
    \n"); + assertRendering( + "- [x] this is *done*\n", + "
      \n
    • " + HTML_CHECKED + " this is done
    • \n
    \n"); - assertRendering("- [ ] do this\n", "
      \n
    • " + HTML_UNCHECKED + " do this
    • \n
    \n"); + assertRendering( + "- [ ] do this\n", "
      \n
    • " + HTML_UNCHECKED + " do this
    • \n
    \n"); - assertRendering("- [x] foo\n" + - " - [ ] bar\n" + - " - [x] baz\n" + - "- [ ] bim", - "
      \n" + - "
    • " + HTML_CHECKED + " foo\n" + - "
        \n" + - "
      • " + HTML_UNCHECKED + " bar
      • \n" + - "
      • " + HTML_CHECKED + " baz
      • \n" + - "
      \n" + - "
    • \n" + - "
    • " + HTML_UNCHECKED + " bim
    • \n" + - "
    \n"); + assertRendering( + "- [x] foo\n" + " - [ ] bar\n" + " - [x] baz\n" + "- [ ] bim", + "
      \n" + + "
    • " + + HTML_CHECKED + + " foo\n" + + "
        \n" + + "
      • " + + HTML_UNCHECKED + + " bar
      • \n" + + "
      • " + + HTML_CHECKED + + " baz
      • \n" + + "
      \n" + + "
    • \n" + + "
    • " + + HTML_UNCHECKED + + " bim
    • \n" + + "
    \n"); - assertRendering("* [ ] do this\n* [ ] and this", - "
      \n
    • " + HTML_UNCHECKED + " do this
    • \n
    • " + HTML_UNCHECKED + " and this
    • \n
    \n"); + assertRendering( + "* [ ] do this\n* [ ] and this", + "
      \n
    • " + + HTML_UNCHECKED + + " do this
    • \n
    • " + + HTML_UNCHECKED + + " and this
    • \n
    \n"); - assertRendering("+ [x] one\n" + - " - [ ] two\n" + - " * [x] three\n", - "
      \n" + - "
    • " + HTML_CHECKED + " one\n" + - "
        \n" + - "
      • " + HTML_UNCHECKED + " two\n" + - "
          \n" + - "
        • " + HTML_CHECKED + " three
        • \n" + - "
        \n" + - "
      • \n" + - "
      \n" + - "
    • \n" + - "
    \n"); + assertRendering( + "+ [x] one\n" + " - [ ] two\n" + " * [x] three\n", + "
      \n" + + "
    • " + + HTML_CHECKED + + " one\n" + + "
        \n" + + "
      • " + + HTML_UNCHECKED + + " two\n" + + "
          \n" + + "
        • " + + HTML_CHECKED + + " three
        • \n" + + "
        \n" + + "
      • \n" + + "
      \n" + + "
    • \n" + + "
    \n"); - assertRendering("TODO list\n" + - "---------\n" + - "- [ ] first task\n" + - "- [x] second task\n" + - "- [ ] third task\n\n" + - "Let me know when you are finished", - "

    TODO list

    \n" + - "
      \n" + - "
    • " + HTML_UNCHECKED + " first task
    • \n" + - "
    • " + HTML_CHECKED + " second task
    • \n" + - "
    • " + HTML_UNCHECKED + " third task
    • \n" + - "
    \n" + - "

    Let me know when you are finished

    \n"); + assertRendering( + "TODO list\n" + + "---------\n" + + "- [ ] first task\n" + + "- [x] second task\n" + + "- [ ] third task\n\n" + + "Let me know when you are finished", + "

    TODO list

    \n" + + "
      \n" + + "
    • " + + HTML_UNCHECKED + + " first task
    • \n" + + "
    • " + + HTML_CHECKED + + " second task
    • \n" + + "
    • " + + HTML_UNCHECKED + + " third task
    • \n" + + "
    \n" + + "

    Let me know when you are finished

    \n"); } @Test public void notListItem() { assertRendering("[x] this is not a task\n", "

    [x] this is not a task

    \n"); - assertRendering(" [ ] this is not a task either\n", "

    [ ] this is not a task either

    \n"); + assertRendering( + " [ ] this is not a task either\n", "

    [ ] this is not a task either

    \n"); } @Test public void notValidTaskFormat() { assertRendering("- [x]no space\n", "
      \n
    • [x]no space
    • \n
    \n"); - assertRendering("- [O] is not a _task_\n", "
      \n
    • [O] is not a task
    • \n
    \n"); + assertRendering( + "- [O] is not a _task_\n", "
      \n
    • [O] is not a task
    • \n
    \n"); assertRendering("* [] neither is this\n", "
      \n
    • [] neither is this
    • \n
    \n"); - assertRendering("* [ ] nor this\n" + - "* [XX] nor this\n", + assertRendering( + "* [ ] nor this\n" + "* [XX] nor this\n", "
      \n
    • [ ] nor this
    • \n
    • [XX] nor this
    • \n
    \n"); assertRendering("+ [x]] is not a task\n", "
      \n
    • [x]] is not a task
    • \n
    \n"); assertRendering("- [x isn't\n", "
      \n
    • [x isn't
    • \n
    \n"); @@ -91,7 +120,9 @@ public void notValidTaskFormat() { assertRendering("+ (x) sorry no\n", "
      \n
    • (x) sorry no
    • \n
    \n"); assertRendering("+ {x} sorry not sorry\n", "
      \n
    • {x} sorry not sorry
    • \n
    \n"); assertRendering("+ [[x]] nooo\n", "
      \n
    • [[x]] nooo
    • \n
    \n"); - assertRendering("+ text before [x] is not a task\n", "
      \n
    • text before [x] is not a task
    • \n
    \n"); + assertRendering( + "+ text before [x] is not a task\n", + "
      \n
    • text before [x] is not a task
    • \n
    \n"); assertRendering("* [x] \n* [ ] \n", "
      \n
    • [x]
    • \n
    • [ ]
    • \n
    \n"); } diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterBlock.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterBlock.java index 0d9aba2d3..5c97d57d2 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterBlock.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterBlock.java @@ -2,5 +2,4 @@ import org.commonmark.node.CustomBlock; -public class YamlFrontMatterBlock extends CustomBlock { -} +public class YamlFrontMatterBlock extends CustomBlock {} diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java index 99ca17708..9bb1b4528 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java @@ -13,19 +13,18 @@ /** * Extension for YAML-like metadata. - *

    - * Create it with {@link #create()} and then configure it on the builders - * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, - * {@link HtmlRenderer.Builder#extensions(Iterable)}). - *

    - *

    - * The parsed metadata is turned into {@link YamlFrontMatterNode}. You can access the metadata using {@link YamlFrontMatterVisitor}. - *

    + * + *

    Create it with {@link #create()} and then configure it on the builders ({@link + * org.commonmark.parser.Parser.Builder#extensions(Iterable)}, {@link + * HtmlRenderer.Builder#extensions(Iterable)}). + * + *

    The parsed metadata is turned into {@link YamlFrontMatterNode}. You can access the metadata + * using {@link YamlFrontMatterVisitor}. */ -public class YamlFrontMatterExtension implements Parser.ParserExtension, MarkdownRenderer.MarkdownRendererExtension { +public class YamlFrontMatterExtension + implements Parser.ParserExtension, MarkdownRenderer.MarkdownRendererExtension { - private YamlFrontMatterExtension() { - } + private YamlFrontMatterExtension() {} @Override public void extend(Parser.Builder parserBuilder) { @@ -38,16 +37,17 @@ public static Extension create() { @Override public void extend(MarkdownRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new MarkdownNodeRendererFactory() { - @Override - public NodeRenderer create(MarkdownNodeRendererContext context) { - return new YamlFrontMatterMarkdownNodeRenderer(context); - } + rendererBuilder.nodeRendererFactory( + new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new YamlFrontMatterMarkdownNodeRenderer(context); + } - @Override - public Set getSpecialCharacters() { - return Set.of(); - } - }); + @Override + public Set getSpecialCharacters() { + return Set.of(); + } + }); } } diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterNode.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterNode.java index 20eb3baf7..e5fe473ad 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterNode.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterNode.java @@ -1,8 +1,7 @@ package org.commonmark.ext.front.matter; -import org.commonmark.node.CustomNode; - import java.util.List; +import org.commonmark.node.CustomNode; public class YamlFrontMatterNode extends CustomNode { private String key; diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterVisitor.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterVisitor.java index 1c23966f5..e49e22c0d 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterVisitor.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterVisitor.java @@ -1,11 +1,10 @@ package org.commonmark.ext.front.matter; -import org.commonmark.node.AbstractVisitor; -import org.commonmark.node.CustomNode; - import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.commonmark.node.AbstractVisitor; +import org.commonmark.node.CustomNode; public class YamlFrontMatterVisitor extends AbstractVisitor { private Map> data; @@ -17,7 +16,9 @@ public YamlFrontMatterVisitor() { @Override public void visit(CustomNode customNode) { if (customNode instanceof YamlFrontMatterNode) { - data.put(((YamlFrontMatterNode) customNode).getKey(), ((YamlFrontMatterNode) customNode).getValues()); + data.put( + ((YamlFrontMatterNode) customNode).getKey(), + ((YamlFrontMatterNode) customNode).getValues()); } else { super.visit(customNode); } diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java index f7f03c001..7d14b403b 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java @@ -1,18 +1,18 @@ package org.commonmark.ext.front.matter.internal; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.commonmark.ext.front.matter.YamlFrontMatterBlock; import org.commonmark.ext.front.matter.YamlFrontMatterNode; import org.commonmark.node.Block; import org.commonmark.node.Document; import org.commonmark.parser.block.*; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - public class YamlFrontMatterBlockParser extends AbstractBlockParser { - private static final Pattern REGEX_METADATA = Pattern.compile("^[ ]{0,3}([A-Za-z0-9._-]+):\\s*(.*)"); + private static final Pattern REGEX_METADATA = + Pattern.compile("^[ ]{0,3}([A-Za-z0-9._-]+):\\s*(.*)"); private static final Pattern REGEX_METADATA_LIST = Pattern.compile("^[ ]+-\\s*(.*)"); private static final Pattern REGEX_METADATA_LITERAL = Pattern.compile("^\\s*(.*)"); private static final Pattern REGEX_BEGIN = Pattern.compile("^-{3}(\\s.*)?"); @@ -95,9 +95,7 @@ private static String parseString(String s) { } else if (s.startsWith("\"") && s.endsWith("\"")) { String inner = s.substring(1, s.length() - 1); // Only support escaped `\` and `"`, nothing else. - return inner - .replace("\\\"", "\"") - .replace("\\\\", "\\"); + return inner.replace("\\\"", "\"").replace("\\\\", "\\"); } else { return s; } @@ -109,9 +107,11 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar CharSequence line = state.getLine().getContent(); BlockParser parentParser = matchedBlockParser.getMatchedBlockParser(); // check whether this line is the first line of whole document or not - if (parentParser.getBlock() instanceof Document && parentParser.getBlock().getFirstChild() == null && - REGEX_BEGIN.matcher(line).matches()) { - return BlockStart.of(new YamlFrontMatterBlockParser()).atIndex(state.getNextNonSpaceIndex()); + if (parentParser.getBlock() instanceof Document + && parentParser.getBlock().getFirstChild() == null + && REGEX_BEGIN.matcher(line).matches()) { + return BlockStart.of(new YamlFrontMatterBlockParser()) + .atIndex(state.getNextNonSpaceIndex()); } return BlockStart.none(); diff --git a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterMarkdownRendererTest.java b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterMarkdownRendererTest.java index 987513ea4..b5bd06058 100644 --- a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterMarkdownRendererTest.java +++ b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterMarkdownRendererTest.java @@ -1,5 +1,8 @@ package org.commonmark.ext.front.matter; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; import org.commonmark.Extension; import org.commonmark.node.Document; import org.commonmark.node.Node; @@ -9,15 +12,12 @@ import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.jupiter.api.Test; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - public class YamlFrontMatterMarkdownRendererTest { private static final List EXTENSIONS = List.of(YamlFrontMatterExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = + MarkdownRenderer.builder().extensions(EXTENSIONS).build(); // ===== Round-trip tests (parse string -> render -> compare to input) ===== @@ -43,7 +43,8 @@ public void testRoundTripListValues() { @Test public void testRoundTripLiteralBlock() { - assertRoundTrip("---\ndescription: |\n first line\n second line\n---\n\nMarkdown content\n"); + assertRoundTrip( + "---\ndescription: |\n first line\n second line\n---\n\nMarkdown content\n"); } @Test @@ -84,7 +85,9 @@ public void testRoundTripEmptyFrontmatter() { @Test public void testProgrammaticallyBuilt() { - var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("title", List.of("My Document")))); + var doc = + buildDocumentWithFrontMatter( + List.of(new YamlFrontMatterNode("title", List.of("My Document")))); assertRenderedEquals(doc, "---\ntitle: My Document\n---\n\nMarkdown content\n"); } @@ -93,29 +96,40 @@ public void testProgrammaticallyBuilt() { @Test public void testValueWithColonSpace() { - var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("value with a: colon inside")))); + var doc = + buildDocumentWithFrontMatter( + List.of( + new YamlFrontMatterNode( + "key", List.of("value with a: colon inside")))); - assertRenderedEquals(doc, "---\nkey: 'value with a: colon inside'\n---\n\nMarkdown content\n"); + assertRenderedEquals( + doc, "---\nkey: 'value with a: colon inside'\n---\n\nMarkdown content\n"); } @Test public void testValueWithColonNoSpace() { // Colon without trailing space is fine unquoted (e.g. timestamps, URLs) - var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("time", List.of("12:30:00")))); + var doc = + buildDocumentWithFrontMatter( + List.of(new YamlFrontMatterNode("time", List.of("12:30:00")))); assertRenderedEquals(doc, "---\ntime: 12:30:00\n---\n\nMarkdown content\n"); } @Test public void testValueStartingWithDash() { - var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("- not a list")))); + var doc = + buildDocumentWithFrontMatter( + List.of(new YamlFrontMatterNode("key", List.of("- not a list")))); assertRenderedEquals(doc, "---\nkey: '- not a list'\n---\n\nMarkdown content\n"); } @Test public void testValueStartingWithUnmatchedBracket() { - var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("[broken")))); + var doc = + buildDocumentWithFrontMatter( + List.of(new YamlFrontMatterNode("key", List.of("[broken")))); assertRenderedEquals(doc, "---\nkey: '[broken'\n---\n\nMarkdown content\n"); } @@ -123,14 +137,18 @@ public void testValueStartingWithUnmatchedBracket() { @Test public void testValueStartingWithMatchedBrackets() { // Valid flow list - should NOT be quoted - var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("flowList", List.of("[1, 2, 3]")))); + var doc = + buildDocumentWithFrontMatter( + List.of(new YamlFrontMatterNode("flowList", List.of("[1, 2, 3]")))); assertRenderedEquals(doc, "---\nflowList: [1, 2, 3]\n---\n\nMarkdown content\n"); } @Test public void testValueStartingWithUnmatchedBrace() { - var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("{broken")))); + var doc = + buildDocumentWithFrontMatter( + List.of(new YamlFrontMatterNode("key", List.of("{broken")))); assertRenderedEquals(doc, "---\nkey: '{broken'\n---\n\nMarkdown content\n"); } @@ -138,35 +156,47 @@ public void testValueStartingWithUnmatchedBrace() { @Test public void testValueStartingWithMatchedBraces() { // Valid flow mapping - should NOT be quoted - var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("flowMapping", List.of("{key: val}")))); + var doc = + buildDocumentWithFrontMatter( + List.of(new YamlFrontMatterNode("flowMapping", List.of("{key: val}")))); assertRenderedEquals(doc, "---\nflowMapping: {key: val}\n---\n\nMarkdown content\n"); } @Test public void testValueContainingHashComment() { - var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("value # not a comment")))); + var doc = + buildDocumentWithFrontMatter( + List.of(new YamlFrontMatterNode("key", List.of("value # not a comment")))); assertRenderedEquals(doc, "---\nkey: 'value # not a comment'\n---\n\nMarkdown content\n"); } @Test public void testValueContainingApostrophe() { - var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("it's a test")))); + var doc = + buildDocumentWithFrontMatter( + List.of(new YamlFrontMatterNode("key", List.of("it's a test")))); assertRenderedEquals(doc, "---\nkey: 'it''s a test'\n---\n\nMarkdown content\n"); } @Test public void testEmptyStringValue() { - var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("empty", List.of("")))); + var doc = + buildDocumentWithFrontMatter( + List.of(new YamlFrontMatterNode("empty", List.of("")))); assertRenderedEquals(doc, "---\nempty: ''\n---\n\nMarkdown content\n"); } @Test public void testValueStartingWithDoubleQuote() { - var doc = buildDocumentWithFrontMatter(List.of(new YamlFrontMatterNode("key", List.of("\"quotes within value\"")))); + var doc = + buildDocumentWithFrontMatter( + List.of( + new YamlFrontMatterNode( + "key", List.of("\"quotes within value\"")))); assertRenderedEquals(doc, "---\nkey: '\"quotes within value\"'\n---\n\nMarkdown content\n"); } diff --git a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java index e0555dc0a..71da8a936 100644 --- a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java +++ b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java @@ -1,5 +1,10 @@ package org.commonmark.ext.front.matter; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Map; +import java.util.Set; import org.commonmark.Extension; import org.commonmark.node.CustomNode; import org.commonmark.node.Node; @@ -8,24 +13,15 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - public class YamlFrontMatterTest extends RenderingTestCase { private static final Set EXTENSIONS = Set.of(YamlFrontMatterExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().extensions(EXTENSIONS).build(); @Test public void simpleValue() { - final String input = "---" + - "\nhello: world" + - "\n..." + - "\n" + - "\ngreat"; + final String input = "---" + "\nhello: world" + "\n..." + "\n" + "\ngreat"; final String rendered = "

    great

    \n"; Map> data = getFrontMatter(input); @@ -40,11 +36,7 @@ public void simpleValue() { @Test public void emptyValue() { - final String input = "---" + - "\nkey:" + - "\n---" + - "\n" + - "\ngreat"; + final String input = "---" + "\nkey:" + "\n---" + "\n" + "\ngreat"; final String rendered = "

    great

    \n"; Map> data = getFrontMatter(input); @@ -58,13 +50,8 @@ public void emptyValue() { @Test public void listValues() { - final String input = "---" + - "\nlist:" + - "\n - value1" + - "\n - value2" + - "\n..." + - "\n" + - "\ngreat"; + final String input = + "---" + "\nlist:" + "\n - value1" + "\n - value2" + "\n..." + "\n" + "\ngreat"; final String rendered = "

    great

    \n"; Map> data = getFrontMatter(input); @@ -80,13 +67,14 @@ public void listValues() { @Test public void literalValue1() { - final String input = "---" + - "\nliteral: |" + - "\n hello markdown!" + - "\n literal thing..." + - "\n---" + - "\n" + - "\ngreat"; + final String input = + "---" + + "\nliteral: |" + + "\n hello markdown!" + + "\n literal thing..." + + "\n---" + + "\n" + + "\ngreat"; final String rendered = "

    great

    \n"; Map> data = getFrontMatter(input); @@ -101,12 +89,8 @@ public void literalValue1() { @Test public void literalValue2() { - final String input = "---" + - "\nliteral: |" + - "\n - hello markdown!" + - "\n---" + - "\n" + - "\ngreat"; + final String input = + "---" + "\nliteral: |" + "\n - hello markdown!" + "\n---" + "\n" + "\ngreat"; final String rendered = "

    great

    \n"; Map> data = getFrontMatter(input); @@ -121,17 +105,18 @@ public void literalValue2() { @Test public void complexValues() { - final String input = "---" + - "\nsimple: value" + - "\nliteral: |" + - "\n hello markdown!" + - "\n" + - "\n literal literal" + - "\nlist:" + - "\n - value1" + - "\n - value2" + - "\n---" + - "\ngreat"; + final String input = + "---" + + "\nsimple: value" + + "\nliteral: |" + + "\n hello markdown!" + + "\n" + + "\n literal literal" + + "\nlist:" + + "\n - value1" + + "\n - value2" + + "\n---" + + "\ngreat"; final String rendered = "

    great

    \n"; Map> data = getFrontMatter(input); @@ -156,9 +141,7 @@ public void complexValues() { @Test public void empty() { - final String input = "---\n" + - "---\n" + - "test"; + final String input = "---\n" + "---\n" + "test"; final String rendered = "

    test

    \n"; Map> data = getFrontMatter(input); @@ -170,12 +153,10 @@ public void empty() { @Test public void yamlInParagraph() { - final String input = "# hello\n" + - "\nhello markdown world!" + - "\n---" + - "\nhello: world" + - "\n---"; - final String rendered = "

    hello

    \n

    hello markdown world!

    \n

    hello: world

    \n"; + final String input = + "# hello\n" + "\nhello markdown world!" + "\n---" + "\nhello: world" + "\n---"; + final String rendered = + "

    hello

    \n

    hello markdown world!

    \n

    hello: world

    \n"; Map> data = getFrontMatter(input); @@ -186,10 +167,7 @@ public void yamlInParagraph() { @Test public void yamlOnSecondLine() { - final String input = "hello\n" + - "\n---" + - "\nhello: world" + - "\n---"; + final String input = "hello\n" + "\n---" + "\nhello: world" + "\n---"; final String rendered = "

    hello

    \n
    \n

    hello: world

    \n"; Map> data = getFrontMatter(input); @@ -201,8 +179,7 @@ public void yamlOnSecondLine() { @Test public void nonMatchedStartTag() { - final String input = "----\n" + - "test"; + final String input = "----\n" + "test"; final String rendered = "
    \n

    test

    \n"; Map> data = getFrontMatter(input); @@ -214,9 +191,7 @@ public void nonMatchedStartTag() { @Test public void inList() { - final String input = "* ---\n" + - " ---\n" + - "test"; + final String input = "* ---\n" + " ---\n" + "test"; final String rendered = "
      \n
    • \n
      \n
      \n
    • \n
    \n

    test

    \n"; Map> data = getFrontMatter(input); @@ -228,10 +203,7 @@ public void inList() { @Test public void visitorIgnoresOtherCustomNodes() { - final String input = "---" + - "\nhello: world" + - "\n---" + - "\n"; + final String input = "---" + "\nhello: world" + "\n---" + "\n"; YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); Node document = PARSER.parse(input); @@ -246,10 +218,7 @@ public void visitorIgnoresOtherCustomNodes() { @Test public void nodesCanBeModified() { - final String input = "---" + - "\nhello: world" + - "\n---" + - "\n"; + final String input = "---" + "\nhello: world" + "\n---" + "\n"; Node document = PARSER.parse(input); YamlFrontMatterNode node = (YamlFrontMatterNode) document.getFirstChild().getFirstChild(); @@ -267,10 +236,7 @@ public void nodesCanBeModified() { @Test public void dotInKeys() { - final String input = "---" + - "\nms.author: author" + - "\n---" + - "\n"; + final String input = "---" + "\nms.author: author" + "\n---" + "\n"; Map> data = getFrontMatter(input); @@ -282,12 +248,8 @@ public void dotInKeys() { @Test public void singleQuotedLiterals() { - final String input = "---" + - "\nstring: 'It''s me'" + - "\nlist:" + - "\n - 'I''m here'" + - "\n---" + - "\n"; + final String input = + "---" + "\nstring: 'It''s me'" + "\nlist:" + "\n - 'I''m here'" + "\n---" + "\n"; Map> data = getFrontMatter(input); @@ -298,12 +260,13 @@ public void singleQuotedLiterals() { @Test public void doubleQuotedLiteral() { - final String input = "---" + - "\nstring: \"backslash: \\\\ quote: \\\"\"" + - "\nlist:" + - "\n - \"hey\"" + - "\n---" + - "\n"; + final String input = + "---" + + "\nstring: \"backslash: \\\\ quote: \\\"\"" + + "\nlist:" + + "\n - \"hey\"" + + "\n---" + + "\n"; Map> data = getFrontMatter(input); @@ -326,6 +289,5 @@ private Map> getFrontMatter(String input) { } // Custom node for tests - private static class TestNode extends CustomNode { - } + private static class TestNode extends CustomNode {} } diff --git a/commonmark-integration-test/src/main/java/org/commonmark/integration/IntegrationTests.java b/commonmark-integration-test/src/main/java/org/commonmark/integration/IntegrationTests.java index 48e1ee5ba..acd86f4e4 100644 --- a/commonmark-integration-test/src/main/java/org/commonmark/integration/IntegrationTests.java +++ b/commonmark-integration-test/src/main/java/org/commonmark/integration/IntegrationTests.java @@ -9,8 +9,5 @@ // And the javadoc plugin doesn't like if there's no classes with documentation, // -/** - * Module with integration tests. - */ -public class IntegrationTests { -} +/** Module with integration tests. */ +public class IntegrationTests {} diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/BoundsIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/BoundsIntegrationTest.java index f1259b825..0efd375f1 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/BoundsIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/BoundsIntegrationTest.java @@ -1,5 +1,8 @@ package org.commonmark.integration; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.testutil.TestResources; @@ -9,21 +12,14 @@ import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.MethodSource; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests various substrings of the spec examples to check for out of bounds exceptions. - */ +/** Tests various substrings of the spec examples to check for out of bounds exceptions. */ @ParameterizedClass @MethodSource("data") public class BoundsIntegrationTest { private static final Parser PARSER = Parser.builder().build(); - @Parameter - String input; + @Parameter String input; static List data() { return ExampleReader.readExampleSources(TestResources.getSpec()); diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java index 9090c797f..aa5f1f28c 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java @@ -1,5 +1,6 @@ package org.commonmark.integration; +import java.util.List; import org.commonmark.Extension; import org.commonmark.ext.autolink.AutolinkExtension; import org.commonmark.ext.footnotes.FootnotesExtension; @@ -11,18 +12,17 @@ import org.commonmark.ext.ins.InsExtension; import org.commonmark.ext.task.list.items.TaskListItemsExtension; -import java.util.List; - public class Extensions { - static final List ALL_EXTENSIONS = List.of( - AutolinkExtension.create(), - FootnotesExtension.create(), - ImageAttributesExtension.create(), - InsExtension.create(), - AlertsExtension.create(), - StrikethroughExtension.create(), - TablesExtension.create(), - TaskListItemsExtension.create(), - YamlFrontMatterExtension.create()); + static final List ALL_EXTENSIONS = + List.of( + AutolinkExtension.create(), + FootnotesExtension.create(), + ImageAttributesExtension.create(), + InsExtension.create(), + AlertsExtension.create(), + StrikethroughExtension.create(), + TablesExtension.create(), + TaskListItemsExtension.create(), + YamlFrontMatterExtension.create()); } diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java index 523154d2c..345c02d15 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java @@ -5,30 +5,30 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -/** - * Tests to ensure all extensions work well together. - */ +/** Tests to ensure all extensions work well together. */ public class ExtensionsIntegrationTest extends RenderingTestCase { - protected static final Parser PARSER = Parser.builder() - .extensions(Extensions.ALL_EXTENSIONS) - .build(); - protected static final HtmlRenderer RENDERER = HtmlRenderer.builder() - .extensions(Extensions.ALL_EXTENSIONS) - .percentEncodeUrls(true) - .build(); + protected static final Parser PARSER = + Parser.builder().extensions(Extensions.ALL_EXTENSIONS).build(); + protected static final HtmlRenderer RENDERER = + HtmlRenderer.builder() + .extensions(Extensions.ALL_EXTENSIONS) + .percentEncodeUrls(true) + .build(); @Test public void testImageAttributes() { - assertRendering("![text](/url.png){height=5 width=6}", "

    \"text\"

    \n"); + assertRendering( + "![text](/url.png){height=5 width=6}", + "

    \"text\"

    \n"); } @Test public void testTaskListItems() { - assertRendering("- [ ] task to do\n- [x] task done\n", - "
      \n
    • task to do
    • \n" + - "
    • task done
    • \n
    \n"); - + assertRendering( + "- [ ] task to do\n- [x] task done\n", + "
      \n
    • task to do
    • \n" + + "
    • task done
    • \n
    \n"); } @Override diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java index 9e64f9717..a9adbb653 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/MarkdownRendererIntegrationTest.java @@ -1,15 +1,17 @@ package org.commonmark.integration; +import static org.assertj.core.api.Assertions.assertThat; + import org.commonmark.parser.Parser; import org.commonmark.renderer.markdown.MarkdownRenderer; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class MarkdownRendererIntegrationTest { - private static final Parser PARSER = Parser.builder().extensions(Extensions.ALL_EXTENSIONS).build(); - private static final MarkdownRenderer RENDERER = MarkdownRenderer.builder().extensions(Extensions.ALL_EXTENSIONS).build(); + private static final Parser PARSER = + Parser.builder().extensions(Extensions.ALL_EXTENSIONS).build(); + private static final MarkdownRenderer RENDERER = + MarkdownRenderer.builder().extensions(Extensions.ALL_EXTENSIONS).build(); @Test public void testStrikethroughInTable() { diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java index ecc9c2cfd..806dc28b8 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java @@ -1,5 +1,6 @@ package org.commonmark.integration; +import java.util.List; import org.commonmark.testutil.TestResources; import org.commonmark.testutil.example.ExampleReader; import org.openjdk.jmh.annotations.Benchmark; @@ -12,20 +13,21 @@ import org.pegdown.Extensions; import org.pegdown.PegDownProcessor; -import java.util.List; - @State(Scope.Benchmark) public class PegDownBenchmark { private static final String SPEC = TestResources.readAsString(TestResources.getSpec()); - private static final List SPEC_EXAMPLES = ExampleReader.readExampleSources(TestResources.getSpec()); - private static final PegDownProcessor PROCESSOR = new PegDownProcessor(Extensions.FENCED_CODE_BLOCKS); + private static final List SPEC_EXAMPLES = + ExampleReader.readExampleSources(TestResources.getSpec()); + private static final PegDownProcessor PROCESSOR = + new PegDownProcessor(Extensions.FENCED_CODE_BLOCKS); public static void main(String[] args) throws Exception { - Options options = new OptionsBuilder() - .parent(new CommandLineOptions(args)) - .include(PegDownBenchmark.class.getName() + ".*") - .build(); + Options options = + new OptionsBuilder() + .parent(new CommandLineOptions(args)) + .include(PegDownBenchmark.class.getName() + ".*") + .build(); new Runner(options).run(); } @@ -47,5 +49,4 @@ private static long parseAndRender(List examples) { } return length; } - } diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java index 4d18fa937..c43abeec2 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java @@ -3,15 +3,14 @@ import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; -/** - * Spec and all extensions, with source spans enabled. - */ +/** Spec and all extensions, with source spans enabled. */ public class SourceSpanIntegrationTest extends SpecIntegrationTest { - protected static final Parser PARSER = Parser.builder() - .extensions(Extensions.ALL_EXTENSIONS) - .includeSourceSpans(IncludeSourceSpans.BLOCKS) - .build(); + protected static final Parser PARSER = + Parser.builder() + .extensions(Extensions.ALL_EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS) + .build(); @Override protected String render(String source) { diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java index 6dca4024e..0a1a7ae2a 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java @@ -1,22 +1,24 @@ package org.commonmark.integration; -import org.commonmark.renderer.html.HtmlRenderer; +import static org.commonmark.testutil.Asserts.assertRendering; + +import java.util.*; import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.SpecTestCase; import org.junit.jupiter.api.Test; -import java.util.*; - -import static org.commonmark.testutil.Asserts.assertRendering; - -/** - * Tests that the spec examples still render the same with all extensions enabled. - */ +/** Tests that the spec examples still render the same with all extensions enabled. */ public class SpecIntegrationTest extends SpecTestCase { - protected static final Parser PARSER = Parser.builder().extensions(Extensions.ALL_EXTENSIONS).build(); + protected static final Parser PARSER = + Parser.builder().extensions(Extensions.ALL_EXTENSIONS).build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. - protected static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(Extensions.ALL_EXTENSIONS).percentEncodeUrls(true).build(); + protected static final HtmlRenderer RENDERER = + HtmlRenderer.builder() + .extensions(Extensions.ALL_EXTENSIONS) + .percentEncodeUrls(true) + .build(); protected static final Map OVERRIDDEN_EXAMPLES = getOverriddenExamples(); @Test @@ -37,22 +39,35 @@ private static Map getOverriddenExamples() { Map m = new HashMap<>(); // Not a spec autolink because of space, but the resulting text contains a valid URL - m.put("\n", "

    <https://foo.bar/baz bim>

    \n"); + m.put( + "\n", + "

    <https://foo.bar/baz bim>

    \n"); // Not a spec autolink, but the resulting text contains a valid email - m.put("\n", "

    <foo+@bar.example.com>

    \n"); + m.put( + "\n", + "

    <foo+@bar.example.com>

    \n"); - // Not a spec autolink because of unknown scheme, but autolink extension doesn't limit schemes - m.put("\n", "

    <heck://bing.bong>

    \n"); + // Not a spec autolink because of unknown scheme, but autolink extension doesn't limit + // schemes + m.put( + "\n", + "

    <heck://bing.bong>

    \n"); // Not a spec autolink because of spaces, but autolink extension doesn't limit schemes - m.put("< https://foo.bar >\n", "

    < https://foo.bar >

    \n"); + m.put( + "< https://foo.bar >\n", + "

    < https://foo.bar >

    \n"); // Plain autolink - m.put("https://example.com\n", "

    https://example.com

    \n"); + m.put( + "https://example.com\n", + "

    https://example.com

    \n"); // Plain autolink - m.put("foo@bar.example.com\n", "

    foo@bar.example.com

    \n"); + m.put( + "foo@bar.example.com\n", + "

    foo@bar.example.com

    \n"); // YAML front matter block m.put("---\nFoo\n---\nBar\n---\nBaz\n", "

    Bar

    \n

    Baz

    \n"); @@ -60,5 +75,4 @@ private static Map getOverriddenExamples() { return m; } - } diff --git a/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java b/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java index d8132fe25..e7870f411 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java @@ -1,18 +1,17 @@ package org.commonmark.ui; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.renderer.text.TextContentRenderer; - import java.awt.*; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.text.TextContentRenderer; /** - * Simple UI to quickly test out different rendering of CommonMark inputs. - * Similar to commonmark.js dingus. - **/ + * Simple UI to quickly test out different rendering of CommonMark inputs. Similar to commonmark.js dingus. + */ public class DingusApp { private final Parser parser = Parser.builder().build(); @@ -57,21 +56,22 @@ private void run() { input.setLineWrap(true); input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); - input.getDocument().addDocumentListener(new DocumentListener() { - @Override - public void insertUpdate(DocumentEvent e) { - updateOutput(input.getText()); - } + input.getDocument() + .addDocumentListener( + new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + updateOutput(input.getText()); + } - @Override - public void removeUpdate(DocumentEvent e) { - updateOutput(input.getText()); - } + @Override + public void removeUpdate(DocumentEvent e) { + updateOutput(input.getText()); + } - @Override - public void changedUpdate(DocumentEvent e) { - } - }); + @Override + public void changedUpdate(DocumentEvent e) {} + }); tabbedPane.addTab("HTML rendered", htmlVisualRendererOutput); tabbedPane.addTab("HTML source", htmlSourceRendererOutput); @@ -79,10 +79,11 @@ public void changedUpdate(DocumentEvent e) { tabbedPane.addChangeListener(e -> updateOutput(input.getText())); - input.setText("# Example\n" + - "Enter text *here* and see how it renders on the right.\n\n" + - "* Try\n* this\n\n" + - "```\nor this\n```"); + input.setText( + "# Example\n" + + "Enter text *here* and see how it renders on the right.\n\n" + + "* Try\n* this\n\n" + + "```\nor this\n```"); updateOutput(input.getText()); frame.setLayout(new GridLayout()); diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/Asserts.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/Asserts.java index 971a1b4ea..89cfdac38 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/Asserts.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/Asserts.java @@ -3,7 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; public class Asserts { - public static void assertRendering(String source, String expectedRendering, String actualRendering) { + public static void assertRendering( + String source, String expectedRendering, String actualRendering) { // include source for better assertion errors String expected = showTabs(expectedRendering + "\n\n" + source); String actual = showTabs(actualRendering + "\n\n" + source); diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java index d61afbc4a..ca2f85042 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java @@ -1,19 +1,17 @@ package org.commonmark.testutil; +import java.util.List; import org.commonmark.testutil.example.Example; import org.commonmark.testutil.example.ExampleReader; import org.junit.jupiter.params.Parameter; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.MethodSource; -import java.util.List; - @ParameterizedClass @MethodSource("data") public abstract class SpecTestCase { - @Parameter - protected Example example; + @Parameter protected Example example; static List data() { return ExampleReader.readExamples(TestResources.getSpec()); diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java index 5af649a86..1ef86461e 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java @@ -20,13 +20,14 @@ public static URL getGfmSpec() { public static List getRegressions() { return List.of( TestResources.class.getResource("/cmark-regression.txt"), - TestResources.class.getResource("/commonmark.js-regression.txt") - ); + TestResources.class.getResource("/commonmark.js-regression.txt")); } public static String readAsString(URL url) { StringBuilder sb = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { + try (BufferedReader reader = + new BufferedReader( + new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { sb.append(line); diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/example/Example.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/Example.java index 11e87d0aa..39c9f1b63 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/example/Example.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/Example.java @@ -9,7 +9,13 @@ public class Example { private final String source; private final String html; - public Example(String filename, String section, String info, int exampleNumber, String source, String html) { + public Example( + String filename, + String section, + String info, + int exampleNumber, + String source, + String html) { this.filename = filename; this.section = section; this.info = info; diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java index d40a10f63..b6ba8b9c1 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java @@ -10,7 +10,8 @@ import java.util.stream.Collectors; /** - * Reader for files containing examples of CommonMark source and the expected HTML rendering (e.g. spec.txt). + * Reader for files containing examples of CommonMark source and the expected HTML rendering (e.g. + * spec.txt). */ public class ExampleReader { @@ -45,11 +46,15 @@ public static List readExamples(URL url) { public static List readExamples(URL url, String info) { var examples = readExamples(url); - return examples.stream().filter(e -> e.getInfo().contains(info)).collect(Collectors.toList()); + return examples.stream() + .filter(e -> e.getInfo().contains(info)) + .collect(Collectors.toList()); } public static List readExampleObjects(URL url, String info) { - return readExamples(url, info).stream().map(e -> new Object[]{e}).collect(Collectors.toList()); + return readExamples(url, info).stream() + .map(e -> new Object[] {e}) + .collect(Collectors.toList()); } public static List readExampleSources(URL url) { @@ -64,8 +69,8 @@ public static List readExampleSources(URL url) { private List read() throws IOException { resetContents(); - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { processLine(line); @@ -101,8 +106,15 @@ private void processLine(String line) { case HTML: if (line.equals("````````````````````````````````")) { state = State.BEFORE; - examples.add(new Example(filename, section, info, exampleNumber, - source.toString(), html.toString())); + var example = + new Example( + filename, + section, + info, + exampleNumber, + source.toString(), + html.toString()); + examples.add(example); resetContents(); } else { html.append(line).append('\n'); @@ -117,6 +129,8 @@ private void resetContents() { } private enum State { - BEFORE, SOURCE, HTML + BEFORE, + SOURCE, + HTML } } diff --git a/commonmark/src/main/java/org/commonmark/Extension.java b/commonmark/src/main/java/org/commonmark/Extension.java index b0e37456e..d233ca3f4 100644 --- a/commonmark/src/main/java/org/commonmark/Extension.java +++ b/commonmark/src/main/java/org/commonmark/Extension.java @@ -2,10 +2,9 @@ /** * Base interface for a parser/renderer extension. - *

    - * Doesn't have any methods itself, but has specific sub interfaces to - * configure parser/renderer. This base interface is for convenience, so that a list of extensions can be built and then - * used for configuring both the parser and renderer in the same way. + * + *

    Doesn't have any methods itself, but has specific sub interfaces to configure parser/renderer. + * This base interface is for convenience, so that a list of extensions can be built and then used + * for configuring both the parser and renderer in the same way. */ -public interface Extension { -} +public interface Extension {} diff --git a/commonmark/src/main/java/org/commonmark/internal/BlockContent.java b/commonmark/src/main/java/org/commonmark/internal/BlockContent.java index 9a9ce6f44..aa9306280 100644 --- a/commonmark/src/main/java/org/commonmark/internal/BlockContent.java +++ b/commonmark/src/main/java/org/commonmark/internal/BlockContent.java @@ -25,5 +25,4 @@ public void add(CharSequence line) { public String getString() { return sb.toString(); } - } diff --git a/commonmark/src/main/java/org/commonmark/internal/BlockContinueImpl.java b/commonmark/src/main/java/org/commonmark/internal/BlockContinueImpl.java index 6410c2c2d..c34562933 100644 --- a/commonmark/src/main/java/org/commonmark/internal/BlockContinueImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/BlockContinueImpl.java @@ -25,5 +25,4 @@ public int getNewColumn() { public boolean isFinalize() { return finalize; } - } diff --git a/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java b/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java index 572c491f8..b3f473527 100644 --- a/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java @@ -42,7 +42,9 @@ public BlockContinue tryContinue(ParserState state) { private static boolean isMarker(ParserState state, int index) { CharSequence line = state.getLine().getContent(); - return state.getIndent() < Parsing.CODE_BLOCK_INDENT && index < line.length() && line.charAt(index) == '>'; + return state.getIndent() < Parsing.CODE_BLOCK_INDENT + && index < line.length() + && line.charAt(index) == '>'; } public static class Factory extends AbstractBlockParserFactory { diff --git a/commonmark/src/main/java/org/commonmark/internal/Bracket.java b/commonmark/src/main/java/org/commonmark/internal/Bracket.java index c04b6ecda..b4dbb0e9a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Bracket.java +++ b/commonmark/src/main/java/org/commonmark/internal/Bracket.java @@ -3,65 +3,81 @@ import org.commonmark.node.Text; import org.commonmark.parser.beta.Position; -/** - * Opening bracket for links ({@code [}), images ({@code ![}), or links with other markers. - */ +/** Opening bracket for links ({@code [}), images ({@code ![}), or links with other markers. */ public class Bracket { - /** - * The node of a marker such as {@code !} if present, null otherwise. - */ + /** The node of a marker such as {@code !} if present, null otherwise. */ public final Text markerNode; - /** - * The position of the marker if present, null otherwise. - */ + /** The position of the marker if present, null otherwise. */ public final Position markerPosition; - /** - * The node of {@code [}. - */ + /** The node of {@code [}. */ public final Text bracketNode; - /** - * The position of {@code [}. - */ + /** The position of {@code [}. */ public final Position bracketPosition; - /** - * The position of the content (after the opening bracket) - */ + /** The position of the content (after the opening bracket) */ public final Position contentPosition; - /** - * Previous bracket. - */ + /** Previous bracket. */ public final Bracket previous; - /** - * Previous delimiter (emphasis, etc) before this bracket. - */ + /** Previous delimiter (emphasis, etc) before this bracket. */ public final Delimiter previousDelimiter; - /** - * Whether this bracket is allowed to form a link/image (also known as "active"). - */ + /** Whether this bracket is allowed to form a link/image (also known as "active"). */ public boolean allowed = true; /** - * Whether there is an unescaped bracket (opening or closing) after this opening bracket in the text parsed so far. + * Whether there is an unescaped bracket (opening or closing) after this opening bracket in the + * text parsed so far. */ public boolean bracketAfter = false; - static public Bracket link(Text bracketNode, Position bracketPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { - return new Bracket(null, null, bracketNode, bracketPosition, contentPosition, previous, previousDelimiter); + public static Bracket link( + Text bracketNode, + Position bracketPosition, + Position contentPosition, + Bracket previous, + Delimiter previousDelimiter) { + return new Bracket( + null, + null, + bracketNode, + bracketPosition, + contentPosition, + previous, + previousDelimiter); } - static public Bracket withMarker(Text markerNode, Position markerPosition, Text bracketNode, Position bracketPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { - return new Bracket(markerNode, markerPosition, bracketNode, bracketPosition, contentPosition, previous, previousDelimiter); + public static Bracket withMarker( + Text markerNode, + Position markerPosition, + Text bracketNode, + Position bracketPosition, + Position contentPosition, + Bracket previous, + Delimiter previousDelimiter) { + return new Bracket( + markerNode, + markerPosition, + bracketNode, + bracketPosition, + contentPosition, + previous, + previousDelimiter); } - private Bracket(Text markerNode, Position markerPosition, Text bracketNode, Position bracketPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { + private Bracket( + Text markerNode, + Position markerPosition, + Text bracketNode, + Position bracketPosition, + Position contentPosition, + Bracket previous, + Delimiter previousDelimiter) { this.markerNode = markerNode; this.markerPosition = markerPosition; this.bracketNode = bracketNode; diff --git a/commonmark/src/main/java/org/commonmark/internal/Definitions.java b/commonmark/src/main/java/org/commonmark/internal/Definitions.java index 0377842c9..ad77ef6dc 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Definitions.java +++ b/commonmark/src/main/java/org/commonmark/internal/Definitions.java @@ -1,9 +1,8 @@ package org.commonmark.internal; -import org.commonmark.node.DefinitionMap; - import java.util.HashMap; import java.util.Map; +import org.commonmark.node.DefinitionMap; public class Definitions { diff --git a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java index 9083ce3cb..e652e373d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java +++ b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java @@ -1,13 +1,10 @@ package org.commonmark.internal; +import java.util.List; import org.commonmark.node.Text; import org.commonmark.parser.delimiter.DelimiterRun; -import java.util.List; - -/** - * Delimiter (emphasis, strong emphasis or custom emphasis). - */ +/** Delimiter (emphasis, strong emphasis or custom emphasis). */ public class Delimiter implements DelimiterRun { public final List characters; @@ -23,7 +20,12 @@ public class Delimiter implements DelimiterRun { public Delimiter previous; public Delimiter next; - public Delimiter(List characters, char delimiterChar, boolean canOpen, boolean canClose, Delimiter previous) { + public Delimiter( + List characters, + char delimiterChar, + boolean canOpen, + boolean canClose, + Delimiter previous) { this.characters = characters; this.delimiterChar = delimiterChar; this.canOpen = canOpen; @@ -65,7 +67,8 @@ public Text getCloser() { @Override public Iterable getOpeners(int length) { if (!(length >= 1 && length <= length())) { - throw new IllegalArgumentException("length must be between 1 and " + length() + ", was " + length); + throw new IllegalArgumentException( + "length must be between 1 and " + length() + ", was " + length); } return characters.subList(characters.size() - length, characters.size()); @@ -74,7 +77,8 @@ public Iterable getOpeners(int length) { @Override public Iterable getClosers(int length) { if (!(length >= 1 && length <= length())) { - throw new IllegalArgumentException("length must be between 1 and " + length() + ", was " + length); + throw new IllegalArgumentException( + "length must be between 1 and " + length() + ", was " + length); } return characters.subList(0, length); diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentBlockParser.java index 4b51275ee..2d52c5080 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentBlockParser.java @@ -29,5 +29,4 @@ public Document getBlock() { public BlockContinue tryContinue(ParserState state) { return BlockContinue.atIndex(state.getIndex()); } - } diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 17e7b9c84..6565cec4f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -1,5 +1,8 @@ package org.commonmark.internal; +import java.io.IOException; +import java.io.Reader; +import java.util.*; import org.commonmark.internal.util.LineReader; import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; @@ -7,26 +10,24 @@ import org.commonmark.parser.InlineParserFactory; import org.commonmark.parser.SourceLine; import org.commonmark.parser.SourceLines; -import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; +import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.block.*; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.text.Characters; -import java.io.IOException; -import java.io.Reader; -import java.util.*; - public class DocumentParser implements ParserState { - private static final Set> CORE_FACTORY_TYPES = new LinkedHashSet<>(List.of( - BlockQuote.class, - Heading.class, - FencedCodeBlock.class, - HtmlBlock.class, - ThematicBreak.class, - ListBlock.class, - IndentedCodeBlock.class)); + private static final Set> CORE_FACTORY_TYPES = + new LinkedHashSet<>( + List.of( + BlockQuote.class, + Heading.class, + FencedCodeBlock.class, + HtmlBlock.class, + ThematicBreak.class, + ListBlock.class, + IndentedCodeBlock.class)); private static final Map, BlockParserFactory> NODES_TO_CORE_FACTORIES; @@ -44,24 +45,16 @@ public class DocumentParser implements ParserState { private SourceLine line; - /** - * Line index (0-based) - */ + /** Line index (0-based) */ private int lineIndex = -1; - /** - * current index (offset) in input line (0-based) - */ + /** current index (offset) in input line (0-based) */ private int index = 0; - /** - * current column of input line (tab causes column to go to next 4-space tab stop) (0-based) - */ + /** current column of input line (tab causes column to go to next 4-space tab stop) (0-based) */ private int column = 0; - /** - * if the current column is within a tab character (partially consumed tab) - */ + /** if the current column is within a tab character (partially consumed tab) */ private boolean columnIsInTab; private int nextNonSpace = 0; @@ -83,10 +76,15 @@ public class DocumentParser implements ParserState { private final List openBlockParsers = new ArrayList<>(); private final List allBlockParsers = new ArrayList<>(); - public DocumentParser(List blockParserFactories, InlineParserFactory inlineParserFactory, - List inlineContentParserFactories, List delimiterProcessors, - List linkProcessors, Set linkMarkers, - IncludeSourceSpans includeSourceSpans, int maxOpenBlockParsers) { + public DocumentParser( + List blockParserFactories, + InlineParserFactory inlineParserFactory, + List inlineContentParserFactories, + List delimiterProcessors, + List linkProcessors, + Set linkMarkers, + IncludeSourceSpans includeSourceSpans, + int maxOpenBlockParsers) { this.blockParserFactories = blockParserFactories; this.inlineParserFactory = inlineParserFactory; this.inlineContentParserFactories = inlineContentParserFactories; @@ -104,8 +102,11 @@ public static Set> getDefaultBlockParserTypes() { return CORE_FACTORY_TYPES; } - public static List calculateBlockParserFactories(List customBlockParserFactories, Set> enabledBlockTypes) { - // By having the custom factories come first, extensions are able to change behavior of core syntax. + public static List calculateBlockParserFactories( + List customBlockParserFactories, + Set> enabledBlockTypes) { + // By having the custom factories come first, extensions are able to change behavior of core + // syntax. List list = new ArrayList<>(customBlockParserFactories); for (Class blockType : enabledBlockTypes) { list.add(NODES_TO_CORE_FACTORIES.get(blockType)); @@ -116,21 +117,25 @@ public static List calculateBlockParserFactories(List> enabledBlockTypes) { for (Class enabledBlockType : enabledBlockTypes) { if (!NODES_TO_CORE_FACTORIES.containsKey(enabledBlockType)) { - throw new IllegalArgumentException("Can't enable block type " + enabledBlockType + ", possible options are: " + NODES_TO_CORE_FACTORIES.keySet()); + throw new IllegalArgumentException( + "Can't enable block type " + + enabledBlockType + + ", possible options are: " + + NODES_TO_CORE_FACTORIES.keySet()); } } } - /** - * The main parsing function. Returns a parsed document AST. - */ + /** The main parsing function. Returns a parsed document AST. */ public Document parse(String input) { int lineStart = 0; int lineBreak; while ((lineBreak = Characters.findLineBreak(input, lineStart)) != -1) { String line = input.substring(lineStart, lineBreak); parseLine(line, lineStart); - if (lineBreak + 1 < input.length() && input.charAt(lineBreak) == '\r' && input.charAt(lineBreak + 1) == '\n') { + if (lineBreak + 1 < input.length() + && input.charAt(lineBreak) == '\r' + && input.charAt(lineBreak + 1) == '\n') { lineStart = lineBreak + 2; } else { lineStart = lineBreak + 1; @@ -196,14 +201,15 @@ public BlockParser getActiveBlockParser() { } /** - * Analyze a line of text and update the document appropriately. We parse markdown text by calling this on each - * line of input, then finalizing the document. + * Analyze a line of text and update the document appropriately. We parse markdown text by + * calling this on each line of input, then finalizing the document. */ private void parseLine(String ln, int inputIndex) { setLine(ln, inputIndex); // For each containing block, try to parse the associated line start. - // The document will always match, so we can skip the first block parser and start at 1 matches + // The document will always match, so we can skip the first block parser and start at 1 + // matches int matches = 1; for (int i = 1; i < openBlockParsers.size(); i++) { OpenBlockParser openBlockParser = openBlockParsers.get(i); @@ -239,13 +245,16 @@ private void parseLine(String ln, int inputIndex) { // Unless last matched container is a code block, try new container starts, // adding children to the last matched container: - boolean tryBlockStarts = blockParser.getBlock() instanceof Paragraph || blockParser.isContainer(); + boolean tryBlockStarts = + blockParser.getBlock() instanceof Paragraph || blockParser.isContainer(); while (tryBlockStarts) { lastIndex = index; findNextNonSpace(); // this is a little performance optimization: - if (isBlank() || (indent < Parsing.CODE_BLOCK_INDENT && Characters.isLetter(this.line.getContent(), nextNonSpace))) { + if (isBlank() + || (indent < Parsing.CODE_BLOCK_INDENT + && Characters.isLetter(this.line.getContent(), nextNonSpace))) { setNewIndex(nextNonSpace); break; } @@ -259,7 +268,8 @@ private void parseLine(String ln, int inputIndex) { startedNewBlock = true; int sourceIndex = getIndex(); - // We're starting a new block. If we have any previous blocks that need to be closed, we need to do it now. + // We're starting a new block. If we have any previous blocks that need to be closed, we + // need to do it now. if (unmatchedBlocks > 0) { closeBlockParsers(unmatchedBlocks); unmatchedBlocks = 0; @@ -272,11 +282,15 @@ private void parseLine(String ln, int inputIndex) { } List replacedSourceSpans = null; - if (blockStart.getReplaceParagraphLines() >= 1 || blockStart.isReplaceActiveBlockParser()) { + if (blockStart.getReplaceParagraphLines() >= 1 + || blockStart.isReplaceActiveBlockParser()) { var activeBlockParser = getActiveBlockParser(); if (activeBlockParser instanceof ParagraphParser) { var paragraphParser = (ParagraphParser) activeBlockParser; - var lines = blockStart.isReplaceActiveBlockParser() ? Integer.MAX_VALUE : blockStart.getReplaceParagraphLines(); + var lines = + blockStart.isReplaceActiveBlockParser() + ? Integer.MAX_VALUE + : blockStart.getReplaceParagraphLines(); replacedSourceSpans = replaceParagraphLines(lines, paragraphParser); } else if (blockStart.isReplaceActiveBlockParser()) { replacedSourceSpans = prepareActiveBlockParserForReplacement(activeBlockParser); @@ -297,8 +311,9 @@ private void parseLine(String ln, int inputIndex) { // appropriate block. // First check for a lazy continuation line - if (!startedNewBlock && !isBlank() && - getActiveBlockParser().canHaveLazyContinuationLines()) { + if (!startedNewBlock + && !isBlank() + && getActiveBlockParser().canHaveLazyContinuationLines()) { openBlockParsers.get(openBlockParsers.size() - 1).sourceIndex = lastIndex; // lazy paragraph continuation addLine(); @@ -324,7 +339,8 @@ private void parseLine(String ln, int inputIndex) { // list item // ``` // - // The first line does not start a paragraph yet, but we still want to record source positions. + // The first line does not start a paragraph yet, but we still want to record source + // positions. addSourceSpans(); } } @@ -416,13 +432,14 @@ private void advance() { } /** - * Add line content to the active block parser. We assume it can accept lines -- that check should be done before - * calling this. + * Add line content to the active block parser. We assume it can accept lines -- that check + * should be done before calling this. */ private void addLine() { CharSequence content; if (columnIsInTab) { - // Our column is in a partially consumed tab. Expand the remaining columns (to the next tab stop) to spaces. + // Our column is in a partially consumed tab. Expand the remaining columns (to the next + // tab stop) to spaces. int afterTab = index + 1; CharSequence rest = line.getContent().subSequence(afterTab, line.getContent().length()); int spaces = Parsing.columnsToNextTabStop(column); @@ -438,8 +455,10 @@ private void addLine() { content = line.getContent().subSequence(index, line.getContent().length()); } SourceSpan sourceSpan = null; - if (includeSourceSpans == IncludeSourceSpans.BLOCKS_AND_INLINES && index < line.getSourceSpan().getLength()) { - // Note that if we're in a partially-consumed tab the length of the source span and the content don't match. + if (includeSourceSpans == IncludeSourceSpans.BLOCKS_AND_INLINES + && index < line.getSourceSpan().getLength()) { + // Note that if we're in a partially-consumed tab the length of the source span and the + // content don't match. sourceSpan = line.getSourceSpan().subSpan(index); } getActiveBlockParser().addLine(SourceLine.of(content, sourceSpan)); @@ -448,15 +467,17 @@ private void addLine() { private void addSourceSpans() { if (includeSourceSpans != IncludeSourceSpans.NONE) { - // Don't add source spans for Document itself (it would get the whole source text), so start at 1, not 0 + // Don't add source spans for Document itself (it would get the whole source text), so + // start at 1, not 0 for (int i = 1; i < openBlockParsers.size(); i++) { var openBlockParser = openBlockParsers.get(i); - // In case of a lazy continuation line, the index is less than where the block parser would expect the - // contents to start, so let's use whichever is smaller. + // In case of a lazy continuation line, the index is less than where the block + // parser would expect the contents to start, so let's use whichever is smaller. int blockIndex = Math.min(openBlockParser.sourceIndex, index); int length = line.getContent().length() - blockIndex; if (length != 0) { - openBlockParser.blockParser.addSourceSpan(line.getSourceSpan().subSpan(blockIndex)); + openBlockParser.blockParser.addSourceSpan( + line.getSourceSpan().subSpan(blockIndex)); } } } @@ -477,10 +498,17 @@ private BlockStartImpl findBlockStart(BlockParser blockParser) { } /** - * Walk through a block & children recursively, parsing string content into inline content where appropriate. + * Walk through a block & children recursively, parsing string content into inline content where + * appropriate. */ private void processInlines() { - var context = new InlineParserContextImpl(inlineContentParserFactories, delimiterProcessors, linkProcessors, linkMarkers, definitions); + var context = + new InlineParserContextImpl( + inlineContentParserFactories, + delimiterProcessors, + linkProcessors, + linkMarkers, + definitions); var inlineParser = inlineParserFactory.create(context); for (var blockParser : allBlockParsers) { @@ -489,8 +517,8 @@ private void processInlines() { } /** - * Add block of type tag as a child of the tip. If the tip can't accept children, close and finalize it and try - * its parent, and so on until we find a block that can accept children. + * Add block of type tag as a child of the tip. If the tip can't accept children, close and + * finalize it and try its parent, and so on until we find a block that can accept children. */ private void addChild(OpenBlockParser openBlockParser) { while (!getActiveBlockParser().canContain(openBlockParser.blockParser.getBlock())) { @@ -522,7 +550,8 @@ private List prepareActiveBlockParserForReplacement(BlockParser bloc // Note that we don't want to parse inlines here, as it's getting replaced. deactivateBlockParser(); - // Do this so that source positions are calculated, which we will carry over to the replacing block. + // Do this so that source positions are calculated, which we will carry over to the + // replacing block. blockParser.closeBlock(); blockParser.getBlock().unlink(); return blockParser.getBlock().getSourceSpans(); @@ -538,16 +567,16 @@ private void closeBlockParsers(int count) { for (int i = 0; i < count; i++) { BlockParser blockParser = deactivateBlockParser().blockParser; finalize(blockParser); - // Remember for inline parsing. Note that a lot of blocks don't need inline parsing. We could have a - // separate interface (e.g. BlockParserWithInlines) so that we only have to remember those that actually - // have inlines to parse. + // Remember for inline parsing. Note that a lot of blocks don't need inline parsing. We + // could have a separate interface (e.g. BlockParserWithInlines) so that we only have to + // remember those that actually have inlines to parse. allBlockParsers.add(blockParser); } } /** - * Finalize a block. Close it and do any necessary postprocessing, e.g. setting the content of blocks and - * collecting link reference definitions from paragraphs. + * Finalize a block. Close it and do any necessary postprocessing, e.g. setting the content of + * blocks and collecting link reference definitions from paragraphs. */ private void finalize(BlockParser blockParser) { addDefinitionsFrom(blockParser); @@ -560,9 +589,7 @@ private void addDefinitionsFrom(BlockParser blockParser) { } } - /** - * Prepares the input line replacing {@code \0} - */ + /** Prepares the input line replacing {@code \0} */ private static String prepareLine(String line) { if (line.indexOf('\0') == -1) { return line; diff --git a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java index d550f1d25..042e08f6b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java @@ -1,5 +1,7 @@ package org.commonmark.internal; +import static org.commonmark.internal.util.Escaping.unescapeString; + import org.commonmark.internal.util.Parsing; import org.commonmark.node.Block; import org.commonmark.node.FencedCodeBlock; @@ -7,8 +9,6 @@ import org.commonmark.parser.block.*; import org.commonmark.text.Characters; -import static org.commonmark.internal.util.Escaping.unescapeString; - public class FencedCodeBlockParser extends AbstractBlockParser { private final FencedCodeBlock block = new FencedCodeBlock(); @@ -36,7 +36,9 @@ public BlockContinue tryContinue(ParserState state) { int nextNonSpace = state.getNextNonSpaceIndex(); int newIndex = state.getIndex(); CharSequence line = state.getLine().getContent(); - if (state.getIndent() < Parsing.CODE_BLOCK_INDENT && nextNonSpace < line.length() && tryClosing(line, nextNonSpace)) { + if (state.getIndent() < Parsing.CODE_BLOCK_INDENT + && nextNonSpace < line.length() + && tryClosing(line, nextNonSpace)) { // closing fence - we're at end of line, so we can finalize now return BlockContinue.finished(); } else { @@ -78,17 +80,19 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } int nextNonSpace = state.getNextNonSpaceIndex(); - FencedCodeBlockParser blockParser = checkOpener(state.getLine().getContent(), nextNonSpace, indent); + FencedCodeBlockParser blockParser = + checkOpener(state.getLine().getContent(), nextNonSpace, indent); if (blockParser != null) { - return BlockStart.of(blockParser).atIndex(nextNonSpace + blockParser.block.getOpeningFenceLength()); + return BlockStart.of(blockParser) + .atIndex(nextNonSpace + blockParser.block.getOpeningFenceLength()); } else { return BlockStart.none(); } } } - // spec: A code fence is a sequence of at least three consecutive backtick characters (`) or tildes (~). (Tildes and - // backticks cannot be mixed.) + // spec: A code fence is a sequence of at least three consecutive backtick characters (`) or + // tildes (~). (Tildes and backticks cannot be mixed.) private static FencedCodeBlockParser checkOpener(CharSequence line, int index, int indent) { int backticks = 0; int tildes = 0; @@ -107,7 +111,8 @@ private static FencedCodeBlockParser checkOpener(CharSequence line, int index, i } } if (backticks >= 3 && tildes == 0) { - // spec: If the info string comes after a backtick fence, it may not contain any backtick characters. + // spec: If the info string comes after a backtick fence, it may not contain any + // backtick characters. if (Characters.find('`', line, index + backticks) != -1) { return null; } @@ -120,8 +125,9 @@ private static FencedCodeBlockParser checkOpener(CharSequence line, int index, i } } - // spec: The content of the code block consists of all subsequent lines, until a closing code fence of the same type - // as the code block began with (backticks or tildes), and with at least as many backticks or tildes as the opening + // spec: The content of the code block consists of all subsequent lines, until a closing code + // fence of the same type as the code block began with (backticks or tildes), and with at least + // as many backticks or tildes as the opening // code fence. private boolean tryClosing(CharSequence line, int index) { int fences = Characters.skip(fenceChar, line, index, line.length()) - index; diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index 05f070137..402647e0f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -28,7 +28,8 @@ public Block getBlock() { @Override public BlockContinue tryContinue(ParserState parserState) { - // In both ATX and Setext headings, once we have the heading markup, there's nothing more to parse. + // In both ATX and Setext headings, once we have the heading markup, there's nothing more to + // parse. return BlockContinue.none(); } @@ -48,7 +49,8 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar SourceLine line = state.getLine(); int nextNonSpace = state.getNextNonSpaceIndex(); if (line.getContent().charAt(nextNonSpace) == '#') { - HeadingParser atxHeading = getAtxHeading(line.substring(nextNonSpace, line.getContent().length())); + HeadingParser atxHeading = + getAtxHeading(line.substring(nextNonSpace, line.getContent().length())); if (atxHeading != null) { return BlockStart.of(atxHeading).atIndex(line.getContent().length()); } @@ -68,10 +70,11 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } } - // spec: An ATX heading consists of a string of characters, parsed as inline content, between an opening sequence of - // 1-6 unescaped # characters and an optional closing sequence of any number of unescaped # characters. The opening - // sequence of # characters must be followed by a space or by the end of line. The optional closing sequence of #s - // must be preceded by a space and may be followed by spaces only. + // spec: An ATX heading consists of a string of characters, parsed as inline content, between an + // opening sequence of 1-6 unescaped # characters and an optional closing sequence of any number + // of unescaped # characters. The opening sequence of # characters must be followed by a space + // or by the end of line. The optional closing sequence of #s must be preceded by a space and + // may be followed by spaces only. private static HeadingParser getAtxHeading(SourceLine line) { Scanner scanner = Scanner.of(SourceLines.of(line)); int level = scanner.matchMultiple('#'); @@ -102,7 +105,8 @@ private static HeadingParser getAtxHeading(SourceLine line) { if (hashCanEnd) { scanner.matchMultiple('#'); int whitespace = scanner.whitespace(); - // If there's other characters, the hashes and spaces were part of the heading + // If there's other characters, the hashes and spaces were part of the + // heading if (scanner.hasNext()) { end = scanner.position(); } @@ -132,8 +136,8 @@ private static HeadingParser getAtxHeading(SourceLine line) { return new HeadingParser(level, source); } - // spec: A setext heading underline is a sequence of = characters or a sequence of - characters, with no more than - // 3 spaces indentation and any number of trailing spaces. + // spec: A setext heading underline is a sequence of = characters or a sequence of - characters, + // with no more than 3 spaces indentation and any number of trailing spaces. private static int getSetextHeadingLevel(CharSequence line, int index) { switch (line.charAt(index)) { case '=': diff --git a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java index 123d9ec1f..68848de6b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java @@ -1,13 +1,12 @@ package org.commonmark.internal; +import java.util.regex.Pattern; import org.commonmark.node.Block; import org.commonmark.node.HtmlBlock; import org.commonmark.node.Paragraph; import org.commonmark.parser.SourceLine; import org.commonmark.parser.block.*; -import java.util.regex.Pattern; - public class HtmlBlockParser extends AbstractBlockParser { private static final String TAGNAME = "[A-Za-z][A-Za-z0-9-]*"; @@ -15,63 +14,56 @@ public class HtmlBlockParser extends AbstractBlockParser { private static final String UNQUOTEDVALUE = "[^\"'=<>`\\x00-\\x20]+"; private static final String SINGLEQUOTEDVALUE = "'[^']*'"; private static final String DOUBLEQUOTEDVALUE = "\"[^\"]*\""; - private static final String ATTRIBUTEVALUE = "(?:" + UNQUOTEDVALUE + "|" + SINGLEQUOTEDVALUE - + "|" + DOUBLEQUOTEDVALUE + ")"; - private static final String ATTRIBUTEVALUESPEC = "(?:" + "\\s*=" + "\\s*" + ATTRIBUTEVALUE - + ")"; - private static final String ATTRIBUTE = "(?:" + "\\s+" + ATTRIBUTENAME + ATTRIBUTEVALUESPEC - + "?)"; + private static final String ATTRIBUTEVALUE = + "(?:" + UNQUOTEDVALUE + "|" + SINGLEQUOTEDVALUE + "|" + DOUBLEQUOTEDVALUE + ")"; + private static final String ATTRIBUTEVALUESPEC = + "(?:" + "\\s*=" + "\\s*" + ATTRIBUTEVALUE + ")"; + private static final String ATTRIBUTE = + "(?:" + "\\s+" + ATTRIBUTENAME + ATTRIBUTEVALUESPEC + "?)"; private static final String OPENTAG = "<" + TAGNAME + ATTRIBUTE + "*" + "\\s*/?>"; private static final String CLOSETAG = "]"; - private static final Pattern[][] BLOCK_PATTERNS = new Pattern[][]{ - {null, null}, // not used (no type 0) - { - Pattern.compile("^<(?:script|pre|style|textarea)(?:\\s|>|$)", Pattern.CASE_INSENSITIVE), + private static final Pattern[][] BLOCK_PATTERNS = + new Pattern[][] { + {null, null}, // not used (no type 0) + { + Pattern.compile( + "^<(?:script|pre|style|textarea)(?:\\s|>|$)", Pattern.CASE_INSENSITIVE), Pattern.compile("", Pattern.CASE_INSENSITIVE) - }, - { - Pattern.compile("^") - }, - { - Pattern.compile("^<[?]"), - Pattern.compile("\\?>") - }, - { - Pattern.compile("^") - }, - { - Pattern.compile("^") - }, - { - Pattern.compile("^]|$)", Pattern.CASE_INSENSITIVE), + }, + {Pattern.compile("^")}, + {Pattern.compile("^<[?]"), Pattern.compile("\\?>")}, + {Pattern.compile("^")}, + {Pattern.compile("^")}, + { + Pattern.compile( + "^]|$)", + Pattern.CASE_INSENSITIVE), null // terminated by blank line - }, - { - Pattern.compile("^(?:" + OPENTAG + '|' + CLOSETAG + ")\\s*$", Pattern.CASE_INSENSITIVE), + }, + { + Pattern.compile( + "^(?:" + OPENTAG + '|' + CLOSETAG + ")\\s*$", Pattern.CASE_INSENSITIVE), null // terminated by blank line - } - }; + } + }; private final HtmlBlock block = new HtmlBlock(); private final Pattern closingPattern; @@ -127,14 +119,17 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar if (state.getIndent() < 4 && line.charAt(nextNonSpace) == '<') { for (int blockType = 1; blockType <= 7; blockType++) { // Type 7 can not interrupt a paragraph (not even a lazy one) - if (blockType == 7 && ( - matchedBlockParser.getMatchedBlockParser().getBlock() instanceof Paragraph || - state.getActiveBlockParser().canHaveLazyContinuationLines())) { + if (blockType == 7 + && (matchedBlockParser.getMatchedBlockParser().getBlock() + instanceof Paragraph + || state.getActiveBlockParser() + .canHaveLazyContinuationLines())) { continue; } Pattern opener = BLOCK_PATTERNS[blockType][0]; Pattern closer = BLOCK_PATTERNS[blockType][1]; - boolean matches = opener.matcher(line.subSequence(nextNonSpace, line.length())).find(); + boolean matches = + opener.matcher(line.subSequence(nextNonSpace, line.length())).find(); if (matches) { return BlockStart.of(new HtmlBlockParser(closer)).atIndex(state.getIndex()); } diff --git a/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java index 3598f5615..b05b0a456 100644 --- a/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java @@ -1,5 +1,7 @@ package org.commonmark.internal; +import java.util.ArrayList; +import java.util.List; import org.commonmark.internal.util.Parsing; import org.commonmark.node.Block; import org.commonmark.node.IndentedCodeBlock; @@ -8,9 +10,6 @@ import org.commonmark.parser.block.*; import org.commonmark.text.Characters; -import java.util.ArrayList; -import java.util.List; - public class IndentedCodeBlockParser extends AbstractBlockParser { private final IndentedCodeBlock block = new IndentedCodeBlock(); @@ -62,12 +61,14 @@ public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { // An indented code block cannot interrupt a paragraph. - if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT && !state.isBlank() && !(state.getActiveBlockParser().getBlock() instanceof Paragraph)) { - return BlockStart.of(new IndentedCodeBlockParser()).atColumn(state.getColumn() + Parsing.CODE_BLOCK_INDENT); + if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT + && !state.isBlank() + && !(state.getActiveBlockParser().getBlock() instanceof Paragraph)) { + return BlockStart.of(new IndentedCodeBlockParser()) + .atColumn(state.getColumn() + Parsing.CODE_BLOCK_INDENT); } else { return BlockStart.none(); } } } } - diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java index 233041f62..9b87896fc 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java @@ -1,14 +1,13 @@ package org.commonmark.internal; +import java.util.List; +import java.util.Set; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParserContext; -import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; +import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.delimiter.DelimiterProcessor; -import java.util.List; -import java.util.Set; - public class InlineParserContextImpl implements InlineParserContext { private final List inlineContentParserFactories; @@ -17,11 +16,12 @@ public class InlineParserContextImpl implements InlineParserContext { private final Set linkMarkers; private final Definitions definitions; - public InlineParserContextImpl(List inlineContentParserFactories, - List delimiterProcessors, - List linkProcessors, - Set linkMarkers, - Definitions definitions) { + public InlineParserContextImpl( + List inlineContentParserFactories, + List delimiterProcessors, + List linkProcessors, + Set linkMarkers, + Definitions definitions) { this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; this.linkProcessors = linkProcessors; diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 44422f421..17ecd6d31 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -1,5 +1,6 @@ package org.commonmark.internal; +import java.util.*; import org.commonmark.internal.inline.*; import org.commonmark.internal.util.Escaping; import org.commonmark.internal.util.LinkScanner; @@ -7,13 +8,11 @@ import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.SourceLines; -import org.commonmark.parser.beta.Scanner; import org.commonmark.parser.beta.*; +import org.commonmark.parser.beta.Scanner; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.text.Characters; -import java.util.*; - public class InlineParserImpl implements InlineParser, InlineParserState { private final InlineParserContext context; @@ -29,27 +28,34 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private int trailingSpaces; /** - * Top delimiter (emphasis, strong emphasis or custom emphasis). (Brackets are on a separate stack, different - * from the algorithm described in the spec.) + * Top delimiter (emphasis, strong emphasis or custom emphasis). (Brackets are on a separate + * stack, different from the algorithm described in the spec.) */ private Delimiter lastDelimiter; - /** - * Top opening bracket ([ or ![)). - */ + /** Top opening bracket ([ or ![)). */ private Bracket lastBracket; public InlineParserImpl(InlineParserContext context) { this.context = context; - this.inlineContentParserFactories = calculateInlineContentParserFactories(context.getCustomInlineContentParserFactories()); - this.delimiterProcessors = calculateDelimiterProcessors(context.getCustomDelimiterProcessors()); + this.inlineContentParserFactories = + calculateInlineContentParserFactories( + context.getCustomInlineContentParserFactories()); + this.delimiterProcessors = + calculateDelimiterProcessors(context.getCustomDelimiterProcessors()); this.linkProcessors = calculateLinkProcessors(context.getCustomLinkProcessors()); this.linkMarkers = calculateLinkMarkers(context.getCustomLinkMarkers()); - this.specialCharacters = calculateSpecialCharacters(linkMarkers, this.delimiterProcessors.keySet(), this.inlineContentParserFactories); + this.specialCharacters = + calculateSpecialCharacters( + linkMarkers, + this.delimiterProcessors.keySet(), + this.inlineContentParserFactories); } - private List calculateInlineContentParserFactories(List customFactories) { - // Custom parsers can override built-in parsers if they want, so make sure they are tried first + private List calculateInlineContentParserFactories( + List customFactories) { + // Custom parsers can override built-in parsers if they want, so make sure they are tried + // first var list = new ArrayList<>(customFactories); list.add(new BackslashInlineParser.Factory()); list.add(new BackticksInlineParser.Factory()); @@ -60,20 +66,25 @@ private List calculateInlineContentParserFactories(L } private List calculateLinkProcessors(List linkProcessors) { - // Custom link processors can override the built-in behavior, so make sure they are tried first + // Custom link processors can override the built-in behavior, so make sure they are tried + // first var list = new ArrayList<>(linkProcessors); list.add(new CoreLinkProcessor()); return list; } - private static Map calculateDelimiterProcessors(List delimiterProcessors) { + private static Map calculateDelimiterProcessors( + List delimiterProcessors) { var map = new HashMap(); - addDelimiterProcessors(List.of(new AsteriskDelimiterProcessor(), new UnderscoreDelimiterProcessor()), map); + addDelimiterProcessors( + List.of(new AsteriskDelimiterProcessor(), new UnderscoreDelimiterProcessor()), map); addDelimiterProcessors(delimiterProcessors, map); return map; } - private static void addDelimiterProcessors(Iterable delimiterProcessors, Map map) { + private static void addDelimiterProcessors( + Iterable delimiterProcessors, + Map map) { for (DelimiterProcessor delimiterProcessor : delimiterProcessors) { char opening = delimiterProcessor.getOpeningCharacter(); char closing = delimiterProcessor.getClosingCharacter(); @@ -99,10 +110,14 @@ private static void addDelimiterProcessors(Iterable delimite } } - private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterProcessor toAdd, Map delimiterProcessors) { + private static void addDelimiterProcessorForChar( + char delimiterChar, + DelimiterProcessor toAdd, + Map delimiterProcessors) { DelimiterProcessor existing = delimiterProcessors.put(delimiterChar, toAdd); if (existing != null) { - throw new IllegalArgumentException("Delimiter processor conflict with delimiter char '" + delimiterChar + "'"); + throw new IllegalArgumentException( + "Delimiter processor conflict with delimiter char '" + delimiterChar + "'"); } } @@ -115,9 +130,10 @@ private static BitSet calculateLinkMarkers(Set linkMarkers) { return bitSet; } - private static BitSet calculateSpecialCharacters(BitSet linkMarkers, - Set delimiterCharacters, - List inlineContentParserFactories) { + private static BitSet calculateSpecialCharacters( + BitSet linkMarkers, + Set delimiterCharacters, + List inlineContentParserFactories) { BitSet bitSet = (BitSet) linkMarkers.clone(); for (Character c : delimiterCharacters) { bitSet.set(c); @@ -150,9 +166,7 @@ public Scanner scanner() { return scanner; } - /** - * Parse content in block into inline children, appending them to the block node. - */ + /** Parse content in block into inline children, appending them to the block node. */ @Override public void parse(SourceLines lines, Node block) { reset(lines); @@ -187,9 +201,8 @@ private Text text(SourceLines sourceLines) { } /** - * Parse the next inline element in subject, advancing our position. - * On success, return the new inline node. - * On failure, return null. + * Parse the next inline element in subject, advancing our position. On success, return the new + * inline node. On failure, return null. */ private List parseInline() { char c = scanner.peek(); @@ -230,7 +243,8 @@ private List parseInline() { Node node = parsedInlineImpl.getNode(); scanner.setPosition(parsedInlineImpl.getPosition()); if (includeSourceSpans && node.getSourceSpans().isEmpty()) { - node.setSourceSpans(scanner.getSource(position, scanner.position()).getSourceSpans()); + node.setSourceSpans( + scanner.getSource(position, scanner.position()).getSourceSpans()); } return List.of(node); } else { @@ -252,10 +266,9 @@ private List parseInline() { return List.of(parseText()); } - /** - * Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters. - */ - private List parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { + /** Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters. */ + private List parseDelimiters( + DelimiterProcessor delimiterProcessor, char delimiterChar) { DelimiterData res = scanDelimiters(delimiterProcessor, delimiterChar); if (res == null) { return null; @@ -264,7 +277,8 @@ private List parseDelimiters(DelimiterProcessor delimiterProcess List characters = res.characters; // Add entry to stack for this opener - lastDelimiter = new Delimiter(characters, delimiterChar, res.canOpen, res.canClose, lastDelimiter); + lastDelimiter = + new Delimiter(characters, delimiterChar, res.canOpen, res.canClose, lastDelimiter); if (lastDelimiter.previous != null) { lastDelimiter.previous.next = lastDelimiter; } @@ -272,9 +286,7 @@ private List parseDelimiters(DelimiterProcessor delimiterProcess return characters; } - /** - * Add open bracket to delimiter stack and add a text node to block's children. - */ + /** Add open bracket to delimiter stack and add a text node to block's children. */ private Node parseOpenBracket() { Position start = scanner.position(); scanner.next(); @@ -288,10 +300,7 @@ private Node parseOpenBracket() { return node; } - /** - * If next character is {@code [}, add a bracket to the stack. - * Otherwise, return null. - */ + /** If next character is {@code [}, add a bracket to the stack. Otherwise, return null. */ private List parseLinkMarker() { var markerPosition = scanner.position(); scanner.next(); @@ -302,7 +311,15 @@ private List parseLinkMarker() { var bracketNode = text(scanner.getSource(bracketPosition, contentPosition)); // Add entry to stack for this opener - addBracket(Bracket.withMarker(bangNode, markerPosition, bracketNode, bracketPosition, contentPosition, lastBracket, lastDelimiter)); + addBracket( + Bracket.withMarker( + bangNode, + markerPosition, + bracketNode, + bracketPosition, + contentPosition, + lastBracket, + lastDelimiter)); return List.of(bangNode, bracketNode); } else { return null; @@ -310,8 +327,9 @@ private List parseLinkMarker() { } /** - * Try to match close bracket against an opening in the delimiter stack. Return either a link or image, or a - * plain [ character. If there is a matching delimiter, remove it from the delimiter stack. + * Try to match close bracket against an opening in the delimiter stack. Return either a link or + * image, or a plain [ character. If there is a matching delimiter, remove it from the delimiter + * stack. */ private Node parseCloseBracket() { Position beforeClose = scanner.position(); @@ -379,7 +397,8 @@ private LinkInfo parseLinkInfo(Bracket opener, Position beforeClose) { // Check to see if we have a link (or image, with a ! in front). The different types: // - Inline: `[foo](/uri)` or with optional title `[foo](/uri "title")` // - Reference links - // - Full: `[foo][bar]` (foo is the text and bar is the label that needs to match a reference) + // - Full: `[foo][bar]` (foo is the text and bar is the label that needs to match a + // reference) // - Collapsed: `[foo][]` (foo is both the text and label) // - Shortcut: `[foo]` (foo is both the text and label) @@ -390,14 +409,21 @@ private LinkInfo parseLinkInfo(Bracket opener, Position beforeClose) { var destinationTitle = parseInlineDestinationTitle(scanner); if (destinationTitle != null) { var text = scanner.getSource(opener.contentPosition, beforeClose).getContent(); - return new LinkInfoImpl(opener.markerNode, opener.bracketNode, text, null, destinationTitle.destination, destinationTitle.title, afterClose); + return new LinkInfoImpl( + opener.markerNode, + opener.bracketNode, + text, + null, + destinationTitle.destination, + destinationTitle.title, + afterClose); } // Not an inline link/image, rewind back to after `]`. scanner.setPosition(afterClose); // Maybe a reference link/image like `[foo][bar]`, `[foo][]` or `[foo]`. - // Note that even `[foo](` could be a valid link if foo is a reference, which is why we try this even if the `(` - // failed to be parsed as an inline link/image before. + // Note that even `[foo](` could be a valid link if foo is a reference, which is why we try + // this even if the `(` failed to be parsed as an inline link/image before. // See if there's a link label like `[bar]` or `[]` var label = parseLinkLabel(scanner); @@ -407,17 +433,20 @@ private LinkInfo parseLinkInfo(Bracket opener, Position beforeClose) { } var textIsReference = label == null || label.isEmpty(); if (opener.bracketAfter && textIsReference && opener.markerNode == null) { - // In case of shortcut or collapsed links, the text is used as the reference. But the reference is not allowed to - // contain an unescaped bracket, so if that's the case we don't need to continue. This is an optimization. + // In case of shortcut or collapsed links, the text is used as the reference. But the + // reference is not allowed to contain an unescaped bracket, so if that's the case we + // don't need to continue. This is an optimization. return null; } var text = scanner.getSource(opener.contentPosition, beforeClose).getContent(); - return new LinkInfoImpl(opener.markerNode, opener.bracketNode, text, label, null, null, afterClose); + return new LinkInfoImpl( + opener.markerNode, opener.bracketNode, text, label, null, null, afterClose); } private Node wrapBracket(Bracket opener, Node wrapperNode, boolean includeMarker) { - // Add all nodes between the opening bracket and now (closing bracket) as child nodes of the link + // Add all nodes between the opening bracket and now (closing bracket) as child nodes of the + // link Node n = opener.bracketNode.getNext(); while (n != null) { Node next = n.getNext(); @@ -426,8 +455,12 @@ private Node wrapBracket(Bracket opener, Node wrapperNode, boolean includeMarker } if (includeSourceSpans) { - var startPosition = includeMarker && opener.markerPosition != null ? opener.markerPosition : opener.bracketPosition; - wrapperNode.setSourceSpans(scanner.getSource(startPosition, scanner.position()).getSourceSpans()); + var startPosition = + includeMarker && opener.markerPosition != null + ? opener.markerPosition + : opener.bracketPosition; + wrapperNode.setSourceSpans( + scanner.getSource(startPosition, scanner.position()).getSourceSpans()); } // Process delimiters such as emphasis inside link/image @@ -440,7 +473,8 @@ private Node wrapBracket(Bracket opener, Node wrapperNode, boolean includeMarker opener.bracketNode.unlink(); removeLastBracket(); - // Links within links are not allowed. We found this link, so there can be no other links around it. + // Links within links are not allowed. We found this link, so there can be no other links + // around it. if (opener.markerNode == null) { disallowPreviousLinks(); } @@ -455,24 +489,30 @@ private Node replaceBracket(Bracket opener, Node node, boolean includeMarker) { } if (includeSourceSpans) { - var startPosition = includeMarker && opener.markerPosition != null ? opener.markerPosition : opener.bracketPosition; - node.setSourceSpans(scanner.getSource(startPosition, scanner.position()).getSourceSpans()); + var startPosition = + includeMarker && opener.markerPosition != null + ? opener.markerPosition + : opener.bracketPosition; + node.setSourceSpans( + scanner.getSource(startPosition, scanner.position()).getSourceSpans()); } removeLastBracket(); // Remove nodes that we added since the opener, because we're replacing them - Node n = includeMarker && opener.markerNode != null ? opener.markerNode : opener.bracketNode; + Node n = + includeMarker && opener.markerNode != null ? opener.markerNode : opener.bracketNode; while (n != null) { var next = n.getNext(); n.unlink(); n = next; } - // Links within links are not allowed. We found this link, so there can be no other links around it. - // Note that this makes any syntax like `[foo]` behave the same as built-in links, which is probably a good - // default (it works for footnotes). It might be useful for a `LinkProcessor` to be able to specify the - // behavior; something we could add to `LinkResult` in the future if requested. + // Links within links are not allowed. We found this link, so there can be no other links + // around it. Note that this makes any syntax like `[foo]` behave the same as built-in + // links, which is probably a good default (it works for footnotes). It might be useful for + // a `LinkProcessor` to be able to specify the behavior; something we could add to + // `LinkResult` in the future if requested. if (opener.markerNode == null || !includeMarker) { disallowPreviousLinks(); } @@ -502,9 +542,7 @@ private void disallowPreviousLinks() { } } - /** - * Try to parse the destination and an optional title for an inline link/image. - */ + /** Try to parse the destination and an optional title for an inline link/image. */ private static DestinationTitle parseInlineDestinationTitle(Scanner scanner) { if (!scanner.next('(')) { return null; @@ -531,9 +569,7 @@ private static DestinationTitle parseInlineDestinationTitle(Scanner scanner) { return new DestinationTitle(dest, title); } - /** - * Attempt to parse link destination, returning the string or null if no match. - */ + /** Attempt to parse link destination, returning the string or null if no match. */ private static String parseLinkDestination(Scanner scanner) { char delimiter = scanner.peek(); Position start = scanner.position(); @@ -553,9 +589,7 @@ private static String parseLinkDestination(Scanner scanner) { return Escaping.unescapeString(dest); } - /** - * Attempt to parse link title (sans quotes), returning the string or null if no match. - */ + /** Attempt to parse link title (sans quotes), returning the string or null if no match. */ private static String parseLinkTitle(Scanner scanner) { Position start = scanner.position(); if (!LinkScanner.scanLinkTitle(scanner)) { @@ -568,9 +602,7 @@ private static String parseLinkTitle(Scanner scanner) { return Escaping.unescapeString(title); } - /** - * Attempt to parse a link label, returning the label between the brackets or null. - */ + /** Attempt to parse a link label, returning the label between the brackets or null. */ static String parseLinkLabel(Scanner scanner) { if (!scanner.next('[')) { return null; @@ -608,7 +640,8 @@ private Node parseLineBreak() { } /** - * Parse the next character as plain text, and possibly more if the following characters are non-special. + * Parse the next character as plain text, and possibly more if the following characters are + * non-special. */ private Node parseText() { Position start = scanner.position(); @@ -626,12 +659,14 @@ private Node parseText() { String content = source.getContent(); if (c == '\n') { - // We parsed until the end of the line. Trim any trailing spaces and remember them (for hard line breaks). + // We parsed until the end of the line. Trim any trailing spaces and remember them (for + // hard line breaks). int end = Characters.skipBackwards(' ', content, content.length() - 1, 0) + 1; trailingSpaces = content.length() - end; content = content.substring(0, end); } else if (c == Scanner.END) { - // For the last line, both tabs and spaces are trimmed for some reason (checked with commonmark.js). + // For the last line, both tabs and spaces are trimmed for some reason (checked with + // commonmark.js). int end = Characters.skipSpaceTabBackwards(content, content.length() - 1, 0) + 1; content = content.substring(0, end); } @@ -642,12 +677,14 @@ private Node parseText() { } /** - * Scan a sequence of characters with code delimiterChar, and return information about the number of delimiters - * and whether they are positioned such that they can open and/or close emphasis or strong emphasis. + * Scan a sequence of characters with code delimiterChar, and return information about the + * number of delimiters and whether they are positioned such that they can open and/or close + * emphasis or strong emphasis. * * @return information about delimiter run, or {@code null} */ - private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { + private DelimiterData scanDelimiters( + DelimiterProcessor delimiterProcessor, char delimiterChar) { int before = scanner.peekPreviousCodePoint(); Position start = scanner.position(); @@ -670,15 +707,20 @@ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char int after = scanner.peekCodePoint(); // We could be more lazy here, in most cases we don't need to do every match case. - boolean beforeIsPunctuation = before == Scanner.END || Characters.isPunctuationCodePoint(before); - boolean beforeIsWhitespace = before == Scanner.END || Characters.isWhitespaceCodePoint(before); - boolean afterIsPunctuation = after == Scanner.END || Characters.isPunctuationCodePoint(after); + boolean beforeIsPunctuation = + before == Scanner.END || Characters.isPunctuationCodePoint(before); + boolean beforeIsWhitespace = + before == Scanner.END || Characters.isWhitespaceCodePoint(before); + boolean afterIsPunctuation = + after == Scanner.END || Characters.isPunctuationCodePoint(after); boolean afterIsWhitespace = after == Scanner.END || Characters.isWhitespaceCodePoint(after); - boolean leftFlanking = !afterIsWhitespace && - (!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation); - boolean rightFlanking = !beforeIsWhitespace && - (!beforeIsPunctuation || afterIsWhitespace || afterIsPunctuation); + boolean leftFlanking = + !afterIsWhitespace + && (!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation); + boolean rightFlanking = + !beforeIsWhitespace + && (!beforeIsPunctuation || afterIsWhitespace || afterIsPunctuation); boolean canOpen; boolean canClose; if (delimiterChar == '_') { @@ -718,7 +760,9 @@ private void processDelimiters(Delimiter stackBottom) { boolean openerFound = false; boolean potentialOpenerFound = false; Delimiter opener = closer.previous; - while (opener != null && opener != stackBottom && opener != openersBottom.get(delimiterChar)) { + while (opener != null + && opener != stackBottom + && opener != openersBottom.get(delimiterChar)) { if (opener.canOpen() && opener.delimiterChar == openingDelimiterChar) { potentialOpenerFound = true; usedDelims = delimiterProcessor.process(opener, closer); @@ -762,7 +806,8 @@ private void processDelimiters(Delimiter stackBottom) { removeDelimitersBetween(opener, closer); - // No delimiter characters left to process, so we can remove delimiter and the now empty node. + // No delimiter characters left to process, so we can remove delimiter and the now empty + // node. if (opener.length() == 0) { removeDelimiterAndNodes(opener); } @@ -790,14 +835,16 @@ private void removeDelimitersBetween(Delimiter opener, Delimiter closer) { } /** - * Remove the delimiter and the corresponding text node. For used delimiters, e.g. `*` in `*foo*`. + * Remove the delimiter and the corresponding text node. For used delimiters, e.g. `*` in + * `*foo*`. */ private void removeDelimiterAndNodes(Delimiter delim) { removeDelimiter(delim); } /** - * Remove the delimiter but keep the corresponding node as text. For unused delimiters such as `_` in `foo_bar`. + * Remove the delimiter but keep the corresponding node as text. For unused delimiters such as + * `_` in `foo_bar`. */ private void removeDelimiterKeepNode(Delimiter delim) { removeDelimiter(delim); @@ -897,9 +944,7 @@ private static class DelimiterData { } } - /** - * A destination and optional title for a link or image. - */ + /** A destination and optional title for a link or image. */ private static class DestinationTitle { final String destination; final String title; @@ -920,8 +965,14 @@ private static class LinkInfoImpl implements LinkInfo { private final String title; private final Position afterTextBracket; - private LinkInfoImpl(Text marker, Text openingBracket, String text, String label, - String destination, String title, Position afterTextBracket) { + private LinkInfoImpl( + Text marker, + Text openingBracket, + String text, + String label, + String destination, + String title, + Position afterTextBracket) { this.marker = marker; this.openingBracket = openingBracket; this.text = text; diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index 637d3b111..8725a0612 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -1,5 +1,8 @@ package org.commonmark.internal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import org.commonmark.internal.util.Escaping; import org.commonmark.internal.util.LinkScanner; import org.commonmark.node.LinkReferenceDefinition; @@ -9,14 +12,11 @@ import org.commonmark.parser.beta.Position; import org.commonmark.parser.beta.Scanner; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - /** * Parser for link reference definitions at the beginning of a paragraph. * - * @see Link reference definitions + * @see Link reference + * definitions */ public class LinkReferenceDefinitionParser { @@ -35,8 +35,8 @@ public class LinkReferenceDefinitionParser { public void parse(SourceLine line) { paragraphLines.add(line); if (state == State.PARAGRAPH) { - // We're in a paragraph now. Link reference definitions can only appear at the beginning, so once - // we're in a paragraph, there's no going back. + // We're in a paragraph now. Link reference definitions can only appear at the + // beginning, so once we're in a paragraph, there's no going back. return; } @@ -44,35 +44,41 @@ public void parse(SourceLine line) { while (scanner.hasNext()) { boolean success; switch (state) { - case START_DEFINITION: { - success = startDefinition(scanner); - break; - } - case LABEL: { - success = label(scanner); - break; - } - case DESTINATION: { - success = destination(scanner); - break; - } - case START_TITLE: { - success = startTitle(scanner); - break; - } - case TITLE: { - success = title(scanner); - break; - } - default: { - throw new IllegalStateException("Unknown parsing state: " + state); - } + case START_DEFINITION: + { + success = startDefinition(scanner); + break; + } + case LABEL: + { + success = label(scanner); + break; + } + case DESTINATION: + { + success = destination(scanner); + break; + } + case START_TITLE: + { + success = startTitle(scanner); + break; + } + case TITLE: + { + success = title(scanner); + break; + } + default: + { + throw new IllegalStateException("Unknown parsing state: " + state); + } } // Parsing failed, which means we fall back to treating text as a paragraph. if (!success) { state = State.PARAGRAPH; - // If parsing of the title part failed, we still have a valid reference that we can add, and we need to - // do it before the source span for this line is added. + // If parsing of the title part failed, we still have a valid reference that we can + // add, and we need to do it before the source span for this line is added. finishReference(); return; } @@ -104,16 +110,20 @@ State getState() { } List removeLines(int lines) { - var removedSpans = Collections.unmodifiableList(new ArrayList<>( - sourceSpans.subList(Math.max(sourceSpans.size() - lines, 0), sourceSpans.size()))); + var removedSpans = + Collections.unmodifiableList( + new ArrayList<>( + sourceSpans.subList( + Math.max(sourceSpans.size() - lines, 0), + sourceSpans.size()))); removeLast(lines, paragraphLines); removeLast(lines, sourceSpans); return removedSpans; } private boolean startDefinition(Scanner scanner) { - // Finish any outstanding references now. We don't do this earlier because we need addSourceSpan to have been - // called before we do it. + // Finish any outstanding references now. We don't do this earlier because we need + // addSourceSpan to have been called before we do it. finishReference(); scanner.whitespace(); @@ -175,14 +185,15 @@ private boolean destination(Scanner scanner) { } String rawDestination = scanner.getSource(start, scanner.position()).getContent(); - destination = rawDestination.startsWith("<") ? - rawDestination.substring(1, rawDestination.length() - 1) : - rawDestination; + destination = + rawDestination.startsWith("<") + ? rawDestination.substring(1, rawDestination.length() - 1) + : rawDestination; int whitespace = scanner.whitespace(); if (!scanner.hasNext()) { - // Destination was at end of line, so this is a valid reference for sure (and maybe a title). - // If not at end of line, wait for title to be valid first. + // Destination was at end of line, so this is a valid reference for sure (and maybe a + // title). If not at end of line, wait for title to be valid first. referenceValid = true; paragraphLines.clear(); } else if (whitespace == 0) { @@ -238,7 +249,8 @@ private boolean title(Scanner scanner) { title.append(scanner.getSource(start, scanner.position()).getContent()); if (!scanner.hasNext()) { - // Title ran until the end of line, so continue on next line (until we find the delimiter) + // Title ran until the end of line, so continue on next line (until we find the + // delimiter) title.append('\n'); return true; } diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index fbf034757..f160b10ff 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -1,11 +1,10 @@ package org.commonmark.internal; +import java.util.Objects; import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.block.*; -import java.util.Objects; - public class ListBlockParser extends AbstractBlockParser { private final ListBlock block; @@ -25,10 +24,11 @@ public boolean isContainer() { @Override public boolean canContain(Block childBlock) { if (childBlock instanceof ListItem) { - // Another list item is added to this list block. If the previous line was blank, that means this list block - // is "loose" (not tight). + // Another list item is added to this list block. If the previous line was blank, that + // means this list block is "loose" (not tight). // - // spec: A list is loose if any of its constituent list items are separated by blank lines + // spec: A list is loose if any of its constituent list items are separated by blank + // lines if (hadBlankLine && linesAfterBlank == 1) { block.setTight(false); hadBlankLine = false; @@ -52,16 +52,18 @@ public BlockContinue tryContinue(ParserState state) { } else if (hadBlankLine) { linesAfterBlank++; } - // List blocks themselves don't have any markers, only list items. So try to stay in the list. - // If there is a block start other than list item, canContain makes sure that this list is closed. + // List blocks themselves don't have any markers, only list items. So try to stay in the + // list. If there is a block start other than list item, canContain makes sure that this + // list is closed. return BlockContinue.atIndex(state.getIndex()); } - /** - * Parse a list marker and return data on the marker or null. - */ - private static ListData parseList(CharSequence line, final int markerIndex, final int markerColumn, - final boolean inParagraph) { + /** Parse a list marker and return data on the marker or null. */ + private static ListData parseList( + CharSequence line, + final int markerIndex, + final int markerColumn, + final boolean inParagraph) { ListMarkerData listMarker = parseListMarker(line, markerIndex); if (listMarker == null) { return null; @@ -92,7 +94,8 @@ private static ListData parseList(CharSequence line, final int markerIndex, fina if (inParagraph) { // If the list item is ordered, the start number must be 1 to interrupt a paragraph. - if (listBlock instanceof OrderedList && ((OrderedList) listBlock).getMarkerStartNumber() != 1) { + if (listBlock instanceof OrderedList + && ((OrderedList) listBlock).getMarkerStartNumber() != 1) { return null; } // Empty list item can not interrupt a paragraph. @@ -128,8 +131,8 @@ private static ListMarkerData parseListMarker(CharSequence line, int index) { } } - // spec: An ordered list marker is a sequence of 1-9 arabic digits (0-9), followed by either a `.` character or a - // `)` character. + // spec: An ordered list marker is a sequence of 1-9 arabic digits (0-9), followed by either a + // `.` character or a `)` character. private static ListMarkerData parseOrderedList(CharSequence line, int index) { int digits = 0; int length = line.length(); @@ -184,15 +187,15 @@ private static boolean isSpaceTabOrEnd(CharSequence line, int index) { } /** - * Returns true if the two list items are of the same type, - * with the same delimiter and bullet character. This is used - * in agglomerating list items into lists. + * Returns true if the two list items are of the same type, with the same delimiter and bullet + * character. This is used in agglomerating list items into lists. */ private static boolean listsMatch(ListBlock a, ListBlock b) { if (a instanceof BulletList && b instanceof BulletList) { return Objects.equals(((BulletList) a).getMarker(), ((BulletList) b).getMarker()); } else if (a instanceof OrderedList && b instanceof OrderedList) { - return Objects.equals(((OrderedList) a).getMarkerDelimiter(), ((OrderedList) b).getMarkerDelimiter()); + return Objects.equals( + ((OrderedList) a).getMarkerDelimiter(), ((OrderedList) b).getMarkerDelimiter()); } return false; } @@ -209,20 +212,23 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar int markerIndex = state.getNextNonSpaceIndex(); int markerColumn = state.getColumn() + state.getIndent(); boolean inParagraph = !matchedBlockParser.getParagraphLines().isEmpty(); - ListData listData = parseList(state.getLine().getContent(), markerIndex, markerColumn, inParagraph); + ListData listData = + parseList(state.getLine().getContent(), markerIndex, markerColumn, inParagraph); if (listData == null) { return BlockStart.none(); } int newColumn = listData.contentColumn; - ListItemParser listItemParser = new ListItemParser(state.getIndent(), newColumn - state.getColumn()); + ListItemParser listItemParser = + new ListItemParser(state.getIndent(), newColumn - state.getColumn()); // prepend the list block if needed - if (!(matched instanceof ListBlockParser) || - !(listsMatch((ListBlock) matched.getBlock(), listData.listBlock))) { + if (!(matched instanceof ListBlockParser) + || !(listsMatch((ListBlock) matched.getBlock(), listData.listBlock))) { ListBlockParser listBlockParser = new ListBlockParser(listData.listBlock); - // We start out with assuming a list is tight. If we find a blank line, we set it to loose later. + // We start out with assuming a list is tight. If we find a blank line, we set it to + // loose later. listData.listBlock.setTight(true); return BlockStart.of(listBlockParser, listItemParser).atColumn(newColumn); diff --git a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java index 49722dff2..61549cc4c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java @@ -13,8 +13,8 @@ public class ListItemParser extends AbstractBlockParser { private final ListItem block = new ListItem(); /** - * Minimum number of columns that the content has to be indented (relative to the containing block) to be part of - * this list item. + * Minimum number of columns that the content has to be indented (relative to the containing + * block) to be part of this list item. */ private int contentIndent; @@ -36,8 +36,8 @@ public boolean canContain(Block childBlock) { if (hadBlankLine) { // We saw a blank line in this list item, that means the list block is loose. // - // spec: if any of its constituent list items directly contain two block-level elements with a blank line - // between them + // spec: if any of its constituent list items directly contain two block-level elements + // with a blank line between them Block parent = block.getParent(); if (parent instanceof ListBlock) { ((ListBlock) parent).setTight(false); @@ -59,7 +59,8 @@ public BlockContinue tryContinue(ParserState state) { return BlockContinue.none(); } else { Block activeBlock = state.getActiveBlockParser().getBlock(); - // If the active block is a code block, blank lines in it should not affect if the list is tight. + // If the active block is a code block, blank lines in it should not affect if the + // list is tight. hadBlankLine = activeBlock instanceof Paragraph || activeBlock instanceof ListItem; return BlockContinue.atIndex(state.getNextNonSpaceIndex()); } diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index 27eb1e647..47f69b34a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -1,5 +1,6 @@ package org.commonmark.internal; +import java.util.List; import org.commonmark.node.*; import org.commonmark.parser.InlineParser; import org.commonmark.parser.SourceLine; @@ -8,12 +9,11 @@ import org.commonmark.parser.block.BlockContinue; import org.commonmark.parser.block.ParserState; -import java.util.List; - public class ParagraphParser extends AbstractBlockParser { private final Paragraph block = new Paragraph(); - private final LinkReferenceDefinitionParser linkReferenceDefinitionParser = new LinkReferenceDefinitionParser(); + private final LinkReferenceDefinitionParser linkReferenceDefinitionParser = + new LinkReferenceDefinitionParser(); @Override public boolean canHaveLazyContinuationLines() { diff --git a/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java index 2836e346a..98354b8e4 100644 --- a/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java @@ -1,28 +1,28 @@ package org.commonmark.internal; -import org.commonmark.parser.delimiter.DelimiterProcessor; -import org.commonmark.parser.delimiter.DelimiterRun; - import java.util.LinkedList; import java.util.ListIterator; +import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterRun; /** - * An implementation of DelimiterProcessor that dispatches all calls to two or more other DelimiterProcessors - * depending on the length of the delimiter run. All child DelimiterProcessors must have different minimum - * lengths. A given delimiter run is dispatched to the child with the largest acceptable minimum length. If no - * child is applicable, the one with the largest minimum length is chosen. + * An implementation of DelimiterProcessor that dispatches all calls to two or more other + * DelimiterProcessors depending on the length of the delimiter run. All child DelimiterProcessors + * must have different minimum lengths. A given delimiter run is dispatched to the child with the + * largest acceptable minimum length. If no child is applicable, the one with the largest minimum + * length is chosen. */ class StaggeredDelimiterProcessor implements DelimiterProcessor { private final char delim; private int minLength = 0; - private LinkedList processors = new LinkedList<>(); // in reverse getMinLength order + private LinkedList processors = + new LinkedList<>(); // in reverse getMinLength order StaggeredDelimiterProcessor(char delim) { this.delim = delim; } - @Override public char getOpeningCharacter() { return delim; @@ -51,7 +51,15 @@ void add(DelimiterProcessor dp) { added = true; break; } else if (len == pLen) { - throw new IllegalArgumentException("Cannot add two delimiter processors for char '" + delim + "' and minimum length " + len + "; conflicting processors: " + p + ", " + dp); + throw new IllegalArgumentException( + "Cannot add two delimiter processors for char '" + + delim + + "' and minimum length " + + len + + "; conflicting processors: " + + p + + ", " + + dp); } } if (!added) { diff --git a/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java index 0f0613221..366a84254 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java @@ -41,8 +41,9 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } } - // spec: A line consisting of 0-3 spaces of indentation, followed by a sequence of three or more matching -, _, or * - // characters, each followed optionally by any number of spaces, forms a thematic break. + // spec: A line consisting of 0-3 spaces of indentation, followed by a sequence of three or more + // matching -, _, or * characters, each followed optionally by any number of spaces, forms a + // thematic break. private static boolean isThematicBreak(CharSequence line, int index) { int dashes = 0; int underscores = 0; @@ -68,8 +69,8 @@ private static boolean isThematicBreak(CharSequence line, int index) { } } - return ((dashes >= 3 && underscores == 0 && asterisks == 0) || - (underscores >= 3 && dashes == 0 && asterisks == 0) || - (asterisks >= 3 && dashes == 0 && underscores == 0)); + return ((dashes >= 3 && underscores == 0 && asterisks == 0) + || (underscores >= 3 && dashes == 0 && asterisks == 0) + || (asterisks >= 3 && dashes == 0 && underscores == 0)); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java index a18966e54..4e3e131fe 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -1,23 +1,21 @@ package org.commonmark.internal.inline; +import java.util.Set; +import java.util.regex.Pattern; import org.commonmark.node.Link; import org.commonmark.node.Text; import org.commonmark.parser.SourceLines; import org.commonmark.parser.beta.*; -import java.util.Set; -import java.util.regex.Pattern; - -/** - * Attempt to parse an autolink (URL or email in pointy brackets). - */ +/** Attempt to parse an autolink (URL or email in pointy brackets). */ public class AutolinkInlineParser implements InlineContentParser { - private static final Pattern URI = Pattern - .compile("^[a-zA-Z][a-zA-Z0-9.+-]{1,31}:[^<>\u0000-\u0020]*$"); + private static final Pattern URI = + Pattern.compile("^[a-zA-Z][a-zA-Z0-9.+-]{1,31}:[^<>\u0000-\u0020]*$"); - private static final Pattern EMAIL = Pattern - .compile("^([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$"); + private static final Pattern EMAIL = + Pattern.compile( + "^([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$"); @Override public ParsedInline tryParse(InlineParserState inlineParserState) { diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java index 7baeed4de..c4e8f3aff 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java @@ -1,16 +1,16 @@ package org.commonmark.internal.inline; +import java.util.Set; +import java.util.regex.Pattern; import org.commonmark.internal.util.Escaping; import org.commonmark.node.HardLineBreak; import org.commonmark.node.Text; import org.commonmark.parser.beta.*; -import java.util.Set; -import java.util.regex.Pattern; - /** - * Parse a backslash-escaped special character, adding either the escaped character, a hard line break - * (if the backslash is followed by a newline), or a literal backslash to the block's children. + * Parse a backslash-escaped special character, adding either the escaped character, a hard line + * break (if the backslash is followed by a newline), or a literal backslash to the block's + * children. */ public class BackslashInlineParser implements InlineContentParser { diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java index b8e8984e8..f636d31b4 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -1,15 +1,15 @@ package org.commonmark.internal.inline; +import java.util.Set; import org.commonmark.node.Code; import org.commonmark.node.Text; import org.commonmark.parser.SourceLines; import org.commonmark.parser.beta.*; import org.commonmark.text.Characters; -import java.util.Set; - /** - * Attempt to parse backticks, returning either a backtick code span or a literal sequence of backticks. + * Attempt to parse backticks, returning either a backtick code span or a literal sequence of + * backticks. */ public class BackticksInlineParser implements InlineContentParser { @@ -29,12 +29,13 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { String content = scanner.getSource(afterOpening, beforeClosing).getContent(); content = content.replace('\n', ' '); - // spec: If the resulting string both begins and ends with a space character, but does not consist - // entirely of space characters, a single space character is removed from the front and back. - if (content.length() >= 3 && - content.charAt(0) == ' ' && - content.charAt(content.length() - 1) == ' ' && - Characters.hasNonSpace(content)) { + // spec: If the resulting string both begins and ends with a space character, but + // does not consist entirely of space characters, a single space character is + // removed from the front and back. + if (content.length() >= 3 + && content.charAt(0) == ' ' + && content.charAt(content.length() - 1) == ' ' + && Characters.hasNonSpace(content)) { content = content.substring(1, content.length() - 1); } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/CoreLinkProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/CoreLinkProcessor.java index 528750aba..3c2efe09b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/CoreLinkProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/CoreLinkProcessor.java @@ -28,9 +28,11 @@ public LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContex return LinkResult.none(); } - private static LinkResult process(LinkInfo linkInfo, Scanner scanner, String destination, String title) { + private static LinkResult process( + LinkInfo linkInfo, Scanner scanner, String destination, String title) { if (linkInfo.marker() != null && linkInfo.marker().getLiteral().equals("!")) { - return LinkResult.wrapTextIn(new Image(destination, title), scanner.position()).includeMarker(); + return LinkResult.wrapTextIn(new Image(destination, title), scanner.position()) + .includeMarker(); } return LinkResult.wrapTextIn(new Link(destination, title), scanner.position()); } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java index 493e4299c..9fbedf897 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java @@ -30,9 +30,9 @@ public int getMinLength() { @Override public int process(DelimiterRun openingRun, DelimiterRun closingRun) { // "multiple of 3" rule for internal delimiter runs - if ((openingRun.canClose() || closingRun.canOpen()) && - closingRun.originalLength() % 3 != 0 && - (openingRun.originalLength() + closingRun.originalLength()) % 3 == 0) { + if ((openingRun.canClose() || closingRun.canOpen()) + && closingRun.originalLength() % 3 != 0 + && (openingRun.originalLength() + closingRun.originalLength()) % 3 == 0) { return 0; } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java index c24e60747..e3ed0c08b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java @@ -1,21 +1,21 @@ package org.commonmark.internal.inline; +import java.util.Set; import org.commonmark.internal.util.Html5Entities; import org.commonmark.node.Text; import org.commonmark.parser.beta.*; import org.commonmark.text.AsciiMatcher; -import java.util.Set; - -/** - * Attempts to parse an HTML entity or numeric character reference. - */ +/** Attempts to parse an HTML entity or numeric character reference. */ public class EntityInlineParser implements InlineContentParser { - private static final AsciiMatcher hex = AsciiMatcher.builder().range('0', '9').range('A', 'F').range('a', 'f').build(); + private static final AsciiMatcher hex = + AsciiMatcher.builder().range('0', '9').range('A', 'F').range('a', 'f').build(); private static final AsciiMatcher dec = AsciiMatcher.builder().range('0', '9').build(); - private static final AsciiMatcher entityStart = AsciiMatcher.builder().range('A', 'Z').range('a', 'z').build(); - private static final AsciiMatcher entityContinue = entityStart.newBuilder().range('0', '9').build(); + private static final AsciiMatcher entityStart = + AsciiMatcher.builder().range('A', 'Z').range('a', 'z').build(); + private static final AsciiMatcher entityContinue = + entityStart.newBuilder().range('0', '9').build(); @Override public ParsedInline tryParse(InlineParserState inlineParserState) { diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java index a48ea5022..b359f732b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -1,31 +1,46 @@ package org.commonmark.internal.inline; +import java.util.Set; import org.commonmark.node.HtmlInline; import org.commonmark.parser.beta.*; import org.commonmark.text.AsciiMatcher; -import java.util.Set; - -/** - * Attempt to parse inline HTML. - */ +/** Attempt to parse inline HTML. */ public class HtmlInlineParser implements InlineContentParser { - private static final AsciiMatcher asciiLetter = AsciiMatcher.builder().range('A', 'Z').range('a', 'z').build(); + private static final AsciiMatcher asciiLetter = + AsciiMatcher.builder().range('A', 'Z').range('a', 'z').build(); - // spec: A tag name consists of an ASCII letter followed by zero or more ASCII letters, digits, or hyphens (-). + // spec: A tag name consists of an ASCII letter followed by zero or more ASCII letters, digits, + // or hyphens (-). private static final AsciiMatcher tagNameStart = asciiLetter; - private static final AsciiMatcher tagNameContinue = tagNameStart.newBuilder().range('0', '9').c('-').build(); + private static final AsciiMatcher tagNameContinue = + tagNameStart.newBuilder().range('0', '9').c('-').build(); - // spec: An attribute name consists of an ASCII letter, _, or :, followed by zero or more ASCII letters, digits, + // spec: An attribute name consists of an ASCII letter, _, or :, followed by zero or more ASCII + // letters, digits, // _, ., :, or -. (Note: This is the XML specification restricted to ASCII. HTML5 is laxer.) - private static final AsciiMatcher attributeStart = asciiLetter.newBuilder().c('_').c(':').build(); - private static final AsciiMatcher attributeContinue = attributeStart.newBuilder().range('0', '9').c('.').c('-').build(); - // spec: An unquoted attribute value is a nonempty string of characters not including whitespace, ", ', =, <, >, or `. - private static final AsciiMatcher attributeValueEnd = AsciiMatcher.builder() - .c(' ').c('\t').c('\n').c('\u000B').c('\f').c('\r') - .c('"').c('\'').c('=').c('<').c('>').c('`') - .build(); + private static final AsciiMatcher attributeStart = + asciiLetter.newBuilder().c('_').c(':').build(); + private static final AsciiMatcher attributeContinue = + attributeStart.newBuilder().range('0', '9').c('.').c('-').build(); + // spec: An unquoted attribute value is a nonempty string of characters not including + // whitespace, ", ', =, <, >, or `. + private static final AsciiMatcher attributeValueEnd = + AsciiMatcher.builder() + .c(' ') + .c('\t') + .c('\n') + .c('\u000B') + .c('\f') + .c('\r') + .c('"') + .c('\'') + .c('=') + .c('<') + .c('>') + .c('`') + .build(); @Override public ParsedInline tryParse(InlineParserState inlineParserState) { @@ -77,16 +92,17 @@ private static ParsedInline htmlInline(Position start, Scanner scanner) { } private static boolean tryOpenTag(Scanner scanner) { - // spec: An open tag consists of a < character, a tag name, zero or more attributes, optional whitespace, - // an optional / character, and a > character. + // spec: An open tag consists of a < character, a tag name, zero or more attributes, + // optional whitespace, an optional / character, and a > character. scanner.next(); scanner.match(tagNameContinue); boolean whitespace = scanner.whitespace() >= 1; - // spec: An attribute consists of whitespace, an attribute name, and an optional attribute value specification. + // spec: An attribute consists of whitespace, an attribute name, and an optional attribute + // value specification. while (whitespace && scanner.match(attributeStart) >= 1) { scanner.match(attributeContinue); - // spec: An attribute value specification consists of optional whitespace, a = character, - // optional whitespace, and an attribute value. + // spec: An attribute value specification consists of optional whitespace, a = + // character, optional whitespace, and an attribute value. whitespace = scanner.whitespace() >= 1; if (scanner.next('=')) { scanner.whitespace(); @@ -119,7 +135,8 @@ private static boolean tryOpenTag(Scanner scanner) { } private static boolean tryClosingTag(Scanner scanner) { - // spec: A closing tag consists of the string . + // spec: A closing tag consists of the string . scanner.next(); if (scanner.match(tagNameStart) >= 1) { scanner.match(tagNameContinue); @@ -130,8 +147,8 @@ private static boolean tryClosingTag(Scanner scanner) { } private static boolean tryProcessingInstruction(Scanner scanner) { - // spec: A processing instruction consists of the string , - // and the string ?>. + // spec: A processing instruction consists of the string , and the string ?>. scanner.next(); while (scanner.find('?') > 0) { scanner.next(); @@ -144,8 +161,8 @@ private static boolean tryProcessingInstruction(Scanner scanner) { private static boolean tryComment(Scanner scanner) { // spec: An [HTML comment](@) consists of ``, ``, or ``, and `-->` (see the - // [HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)). + // characters not including the string `-->`, and `-->` (see the [HTML + // spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)). // Skip first `-` scanner.next(); @@ -169,8 +186,8 @@ private static boolean tryComment(Scanner scanner) { } private static boolean tryCdata(Scanner scanner) { - // spec: A CDATA section consists of the string , - // and the string ]]>. + // spec: A CDATA section consists of the string , and the string ]]>. // Skip `[` scanner.next(); @@ -189,8 +206,8 @@ private static boolean tryCdata(Scanner scanner) { } private static boolean tryDeclaration(Scanner scanner) { - // spec: A declaration consists of the string , and the character >. + // spec: A declaration consists of the string , and the character >. scanner.match(asciiLetter); if (scanner.whitespace() <= 0) { return false; diff --git a/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java b/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java index c74f90758..66f779a91 100644 --- a/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java +++ b/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java @@ -1,12 +1,11 @@ package org.commonmark.internal.renderer; -import org.commonmark.node.Node; -import org.commonmark.renderer.NodeRenderer; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.commonmark.node.Node; +import org.commonmark.renderer.NodeRenderer; public class NodeRendererMap { @@ -14,7 +13,8 @@ public class NodeRendererMap { private final Map, NodeRenderer> renderers = new HashMap<>(32); /** - * Set the renderer for each {@link NodeRenderer#getNodeTypes()}, unless there was already a renderer set (first wins). + * Set the renderer for each {@link NodeRenderer#getNodeTypes()}, unless there was already a + * renderer set (first wins). */ public void add(NodeRenderer nodeRenderer) { nodeRenderers.add(nodeRenderer); diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java index caf802a43..a44110500 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java @@ -16,42 +16,47 @@ public class Escaping { private static final Pattern ENTITY_OR_ESCAPED_CHAR = Pattern.compile("\\\\" + ESCAPABLE + '|' + ENTITY, Pattern.CASE_INSENSITIVE); - // From RFC 3986 (see "reserved", "unreserved") except don't escape '[' or ']' to be compatible with JS encodeURI + // From RFC 3986 (see "reserved", "unreserved") except don't escape '[' or ']' to be compatible + // with JS encodeURI private static final Pattern ESCAPE_IN_URI = Pattern.compile("(%[a-fA-F0-9]{0,2}|[^:/?#@!$&'()*+,;=a-zA-Z0-9\\-._~])"); private static final char[] HEX_DIGITS = - new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + new char[] { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; private static final Pattern WHITESPACE = Pattern.compile("[ \t\r\n]+"); - private static final Replacer UNESCAPE_REPLACER = (input, sb) -> { - if (input.charAt(0) == '\\') { - sb.append(input, 1, input.length()); - } else { - sb.append(Html5Entities.entityToString(input)); - } - }; - - private static final Replacer URI_REPLACER = (input, sb) -> { - if (input.startsWith("%")) { - if (input.length() == 3) { - // Already percent-encoded, preserve - sb.append(input); - } else { - // %25 is the percent-encoding for % - sb.append("%25"); - sb.append(input, 1, input.length()); - } - } else { - byte[] bytes = input.getBytes(StandardCharsets.UTF_8); - for (byte b : bytes) { - sb.append('%'); - sb.append(HEX_DIGITS[(b >> 4) & 0xF]); - sb.append(HEX_DIGITS[b & 0xF]); - } - } - }; + private static final Replacer UNESCAPE_REPLACER = + (input, sb) -> { + if (input.charAt(0) == '\\') { + sb.append(input, 1, input.length()); + } else { + sb.append(Html5Entities.entityToString(input)); + } + }; + + private static final Replacer URI_REPLACER = + (input, sb) -> { + if (input.startsWith("%")) { + if (input.length() == 3) { + // Already percent-encoded, preserve + sb.append(input); + } else { + // %25 is the percent-encoding for % + sb.append("%25"); + sb.append(input, 1, input.length()); + } + } else { + byte[] bytes = input.getBytes(StandardCharsets.UTF_8); + for (byte b : bytes) { + sb.append('%'); + sb.append(HEX_DIGITS[(b >> 4) & 0xF]); + sb.append(HEX_DIGITS[b & 0xF]); + } + } + }; public static String escapeHtml(String input) { // Avoid building a new string in the majority of cases (nothing to escape) @@ -89,9 +94,7 @@ public static String escapeHtml(String input) { return sb != null ? sb.toString() : input; } - /** - * Replace entities and backslash escapes with literal characters. - */ + /** Replace entities and backslash escapes with literal characters. */ public static String unescapeString(String s) { if (BACKSLASH_OR_AMP.matcher(s).find()) { return replaceAll(ENTITY_OR_ESCAPED_CHAR, s, UNESCAPE_REPLACER); diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java b/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java index d6034ff14..2e92c9be8 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java @@ -51,7 +51,8 @@ private static Map readEntities() { Map entities = new HashMap<>(); InputStream stream = Html5Entities.class.getResourceAsStream(ENTITY_PATH); Charset charset = StandardCharsets.UTF_8; - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, charset))) { + try (BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(stream, charset))) { String line; while ((line = bufferedReader.readLine()) != null) { if (line.isEmpty()) { @@ -63,7 +64,8 @@ private static Map readEntities() { entities.put(key, value); } } catch (IOException e) { - throw new IllegalStateException("Failed reading data for HTML named character references", e); + throw new IllegalStateException( + "Failed reading data for HTML named character references", e); } entities.put("NewLine", "\n"); return entities; diff --git a/commonmark/src/main/java/org/commonmark/internal/util/LineReader.java b/commonmark/src/main/java/org/commonmark/internal/util/LineReader.java index b44098257..a37e93c92 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/LineReader.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/LineReader.java @@ -5,12 +5,13 @@ import java.io.Reader; /** - * Reads lines from a reader like {@link java.io.BufferedReader} but also returns the line terminators. - *

    - * Line terminators can be either a line feed {@code "\n"}, carriage return {@code "\r"}, or a carriage return followed - * by a line feed {@code "\r\n"}. Call {@link #getLineTerminator()} after {@link #readLine()} to obtain the - * corresponding line terminator. If a stream has a line at the end without a terminator, {@link #getLineTerminator()} - * returns {@code null}. + * Reads lines from a reader like {@link java.io.BufferedReader} but also returns the line + * terminators. + * + *

    Line terminators can be either a line feed {@code "\n"}, carriage return {@code "\r"}, or a + * carriage return followed by a line feed {@code "\r\n"}. Call {@link #getLineTerminator()} after + * {@link #readLine()} to obtain the corresponding line terminator. If a stream has a line at the + * end without a terminator, {@link #getLineTerminator()} returns {@code null}. */ public class LineReader implements Closeable { @@ -34,7 +35,8 @@ public LineReader(Reader reader) { /** * Read a line of text. * - * @return the line, or {@code null} when the end of the stream has been reached and no more lines can be read + * @return the line, or {@code null} when the end of the stream has been reached and no more + * lines can be read */ public String readLine() throws IOException { StringBuilder sb = null; diff --git a/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java b/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java index ffed047e5..b8072ad26 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java @@ -5,9 +5,9 @@ public class LinkScanner { /** - * Attempt to scan the contents of a link label (inside the brackets), stopping after the content or returning false. - * The stopped position can bei either the closing {@code ]}, or the end of the line if the label continues on - * the next line. + * Attempt to scan the contents of a link label (inside the brackets), stopping after the + * content or returning false. The stopped position can bei either the closing {@code ]}, or the + * end of the line if the label continues on the next line. */ public static boolean scanLinkLabelContent(Scanner scanner) { while (scanner.hasNext()) { @@ -21,8 +21,8 @@ public static boolean scanLinkLabelContent(Scanner scanner) { case ']': return true; case '[': - // spec: Unescaped square bracket characters are not allowed inside the opening and closing - // square brackets of link labels. + // spec: Unescaped square bracket characters are not allowed inside the opening + // and closing square brackets of link labels. return false; default: scanner.next(); @@ -31,9 +31,7 @@ public static boolean scanLinkLabelContent(Scanner scanner) { return true; } - /** - * Attempt to scan a link destination, stopping after the destination or returning false. - */ + /** Attempt to scan a link destination, stopping after the destination or returning false. */ public static boolean scanLinkDestination(Scanner scanner) { if (!scanner.hasNext()) { return false; @@ -115,9 +113,9 @@ public static boolean scanLinkTitleContent(Scanner scanner, char endDelimiter) { return true; } - // spec: a nonempty sequence of characters that does not start with <, does not include ASCII space or control - // characters, and includes parentheses only if (a) they are backslash-escaped or (b) they are part of a balanced - // pair of unescaped parentheses + // spec: a nonempty sequence of characters that does not start with <, does not include ASCII + // space or control characters, and includes parentheses only if (a) they are backslash-escaped + // or (b) they are part of a balanced pair of unescaped parentheses private static boolean scanLinkDestinationWithBalancedParens(Scanner scanner) { int parens = 0; boolean empty = true; diff --git a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java index 7edd635d7..d39112e07 100644 --- a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java +++ b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java @@ -2,9 +2,9 @@ /** * Abstract visitor that visits all children by default. - *

    - * Can be used to only process certain nodes. If you override a method and want visiting to descend into children, - * call {@link #visitChildren}. + * + *

    Can be used to only process certain nodes. If you override a method and want visiting to + * descend into children, call {@link #visitChildren}. */ public abstract class AbstractVisitor implements Visitor { @@ -131,8 +131,8 @@ public void visit(CustomNode customNode) { protected void visitChildren(Node parent) { Node node = parent.getFirstChild(); while (node != null) { - // A subclass of this visitor might modify the node, resulting in getNext returning a different node or no - // node after visiting it. So get the next node before visiting. + // A subclass of this visitor might modify the node, resulting in getNext returning a + // different node or no node after visiting it. So get the next node before visiting. Node next = node.getNext(); node.accept(this); node = next; diff --git a/commonmark/src/main/java/org/commonmark/node/Block.java b/commonmark/src/main/java/org/commonmark/node/Block.java index 332346b0e..2434aec62 100644 --- a/commonmark/src/main/java/org/commonmark/node/Block.java +++ b/commonmark/src/main/java/org/commonmark/node/Block.java @@ -1,8 +1,6 @@ package org.commonmark.node; -/** - * Block nodes such as paragraphs, list blocks, code blocks etc. - */ +/** Block nodes such as paragraphs, list blocks, code blocks etc. */ public abstract class Block extends Node { @Override @@ -13,7 +11,8 @@ public Block getParent() { @Override protected void setParent(Node parent) { if (!(parent instanceof Block)) { - throw new IllegalArgumentException("Parent of block must also be block (can not be inline)"); + throw new IllegalArgumentException( + "Parent of block must also be block (can not be inline)"); } super.setParent(parent); } diff --git a/commonmark/src/main/java/org/commonmark/node/BlockQuote.java b/commonmark/src/main/java/org/commonmark/node/BlockQuote.java index f68252398..935abaf48 100644 --- a/commonmark/src/main/java/org/commonmark/node/BlockQuote.java +++ b/commonmark/src/main/java/org/commonmark/node/BlockQuote.java @@ -2,11 +2,12 @@ /** * A block quote, e.g.: + * *

      * > Some quoted text
      * 
    - *

    - * Note that child nodes are themselves blocks, e.g. {@link Paragraph}, {@link ListBlock} etc. + * + *

    Note that child nodes are themselves blocks, e.g. {@link Paragraph}, {@link ListBlock} etc. * * @see CommonMark Spec */ diff --git a/commonmark/src/main/java/org/commonmark/node/BulletList.java b/commonmark/src/main/java/org/commonmark/node/BulletList.java index 014f4d3b2..b5826f2c9 100644 --- a/commonmark/src/main/java/org/commonmark/node/BulletList.java +++ b/commonmark/src/main/java/org/commonmark/node/BulletList.java @@ -2,13 +2,14 @@ /** * A bullet list, e.g.: + * *

      * - One
      * - Two
      * - Three
      * 
    - *

    - * The children are {@link ListItem} blocks, which contain other blocks (or nested lists). + * + *

    The children are {@link ListItem} blocks, which contain other blocks (or nested lists). * * @see CommonMark Spec: List items */ @@ -22,7 +23,8 @@ public void accept(Visitor visitor) { } /** - * @return the bullet list marker that was used, e.g. {@code -}, {@code *} or {@code +}, if available, or null otherwise + * @return the bullet list marker that was used, e.g. {@code -}, {@code *} or {@code +}, if + * available, or null otherwise */ public String getMarker() { return marker; diff --git a/commonmark/src/main/java/org/commonmark/node/Code.java b/commonmark/src/main/java/org/commonmark/node/Code.java index 3b79e0c9c..4de2bc337 100644 --- a/commonmark/src/main/java/org/commonmark/node/Code.java +++ b/commonmark/src/main/java/org/commonmark/node/Code.java @@ -2,6 +2,7 @@ /** * Inline code span, e.g.: + * *

      * Some `inline code`
      * 
    @@ -12,8 +13,7 @@ public class Code extends Node { private String literal; - public Code() { - } + public Code() {} public Code(String literal) { this.literal = literal; @@ -25,8 +25,8 @@ public void accept(Visitor visitor) { } /** - * @return the literal text in the code span (note that it's not necessarily the raw text between tildes, - * e.g. when spaces are stripped) + * @return the literal text in the code span (note that it's not necessarily the raw text + * between tildes, e.g. when spaces are stripped) */ public String getLiteral() { return literal; diff --git a/commonmark/src/main/java/org/commonmark/node/CustomBlock.java b/commonmark/src/main/java/org/commonmark/node/CustomBlock.java index cad88933a..3a167c057 100644 --- a/commonmark/src/main/java/org/commonmark/node/CustomBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/CustomBlock.java @@ -1,7 +1,8 @@ package org.commonmark.node; /** - * A block that extensions can subclass to define custom blocks (not part of the core specification). + * A block that extensions can subclass to define custom blocks (not part of the core + * specification). */ public abstract class CustomBlock extends Block { diff --git a/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java b/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java index 59cb88274..09f2ddf01 100644 --- a/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java +++ b/commonmark/src/main/java/org/commonmark/node/DefinitionMap.java @@ -1,15 +1,14 @@ package org.commonmark.node; -import org.commonmark.internal.util.Escaping; - import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import org.commonmark.internal.util.Escaping; /** - * A map that can be used to store and look up reference definitions by a label. The labels are case-insensitive and - * normalized, the same way as for {@link LinkReferenceDefinition} nodes. + * A map that can be used to store and look up reference definitions by a label. The labels are + * case-insensitive and normalized, the same way as for {@link LinkReferenceDefinition} nodes. * * @param the type of value */ @@ -35,10 +34,10 @@ public void addAll(DefinitionMap that) { } /** - * Store a new definition unless one is already in the map. If there is no definition for that label yet, return null. - * Otherwise, return the existing definition. - *

    - * The label is normalized by the definition map before storing. + * Store a new definition unless one is already in the map. If there is no definition for that + * label yet, return null. Otherwise, return the existing definition. + * + *

    The label is normalized by the definition map before storing. */ public D putIfAbsent(String label, D definition) { String normalizedLabel = Escaping.normalizeLabelContent(label); diff --git a/commonmark/src/main/java/org/commonmark/node/Delimited.java b/commonmark/src/main/java/org/commonmark/node/Delimited.java index ef02c84ad..b6f95b1a6 100644 --- a/commonmark/src/main/java/org/commonmark/node/Delimited.java +++ b/commonmark/src/main/java/org/commonmark/node/Delimited.java @@ -1,8 +1,6 @@ package org.commonmark.node; -/** - * A node that uses delimiters in the source form (e.g. *bold*). - */ +/** A node that uses delimiters in the source form (e.g. *bold*). */ public interface Delimited { /** diff --git a/commonmark/src/main/java/org/commonmark/node/Document.java b/commonmark/src/main/java/org/commonmark/node/Document.java index b4968c206..b5ff45f6e 100644 --- a/commonmark/src/main/java/org/commonmark/node/Document.java +++ b/commonmark/src/main/java/org/commonmark/node/Document.java @@ -1,8 +1,6 @@ package org.commonmark.node; -/** - * The root block of a document, containing the top-level blocks. - */ +/** The root block of a document, containing the top-level blocks. */ public class Document extends Block { @Override diff --git a/commonmark/src/main/java/org/commonmark/node/Emphasis.java b/commonmark/src/main/java/org/commonmark/node/Emphasis.java index 5efc8c327..57e819a91 100644 --- a/commonmark/src/main/java/org/commonmark/node/Emphasis.java +++ b/commonmark/src/main/java/org/commonmark/node/Emphasis.java @@ -2,18 +2,19 @@ /** * Emphasis, e.g.: + * *

      * Some *emphasis* or _emphasis_
      * 
    * - * @see CommonMark Spec: Emphasis and strong emphasis + * @see CommonMark Spec: + * Emphasis and strong emphasis */ public class Emphasis extends Node implements Delimited { private String delimiter; - public Emphasis() { - } + public Emphasis() {} public Emphasis(String delimiter) { this.delimiter = delimiter; diff --git a/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java b/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java index 0e279a470..22922cf33 100644 --- a/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/FencedCodeBlock.java @@ -2,12 +2,14 @@ /** * A fenced code block, e.g.: + * *
      * ```
      * foo
      * bar
      * ```
      * 
    + * *

    * * @see CommonMark Spec @@ -28,7 +30,8 @@ public void accept(Visitor visitor) { } /** - * @return the fence character that was used, e.g. {@code `} or {@code ~}, if available, or null otherwise + * @return the fence character that was used, e.g. {@code `} or {@code ~}, if available, or null + * otherwise */ public String getFenceCharacter() { return fenceCharacter; @@ -39,8 +42,8 @@ public void setFenceCharacter(String fenceCharacter) { } /** - * @return the length of the opening fence (how many of {{@link #getFenceCharacter()}} were used to start the code - * block) if available, or null otherwise + * @return the length of the opening fence (how many of {{@link #getFenceCharacter()}} were used + * to start the code block) if available, or null otherwise */ public Integer getOpeningFenceLength() { return openingFenceLength; @@ -55,8 +58,8 @@ public void setOpeningFenceLength(Integer openingFenceLength) { } /** - * @return the length of the closing fence (how many of {@link #getFenceCharacter()} were used to end the code - * block) if available, or null otherwise + * @return the length of the closing fence (how many of {@link #getFenceCharacter()} were used + * to end the code block) if available, or null otherwise */ public Integer getClosingFenceLength() { return closingFenceLength; @@ -102,7 +105,9 @@ public void setLiteral(String literal) { */ @Deprecated public char getFenceChar() { - return fenceCharacter != null && !fenceCharacter.isEmpty() ? fenceCharacter.charAt(0) : '\0'; + return fenceCharacter != null && !fenceCharacter.isEmpty() + ? fenceCharacter.charAt(0) + : '\0'; } /** @@ -132,7 +137,8 @@ public void setFenceLength(int fenceLength) { private static void checkFenceLengths(Integer openingFenceLength, Integer closingFenceLength) { if (openingFenceLength != null && closingFenceLength != null) { if (closingFenceLength < openingFenceLength) { - throw new IllegalArgumentException("fence lengths required to be: closingFenceLength >= openingFenceLength"); + throw new IllegalArgumentException( + "fence lengths required to be: closingFenceLength >= openingFenceLength"); } } } diff --git a/commonmark/src/main/java/org/commonmark/node/HardLineBreak.java b/commonmark/src/main/java/org/commonmark/node/HardLineBreak.java index 28874ec01..9d1c059b9 100644 --- a/commonmark/src/main/java/org/commonmark/node/HardLineBreak.java +++ b/commonmark/src/main/java/org/commonmark/node/HardLineBreak.java @@ -2,10 +2,12 @@ /** * A hard line break, e.g.: + * *

      * line\
      * break
      * 
    + * *

    * * @see CommonMark Spec diff --git a/commonmark/src/main/java/org/commonmark/node/Heading.java b/commonmark/src/main/java/org/commonmark/node/Heading.java index 5369d8739..92d275761 100644 --- a/commonmark/src/main/java/org/commonmark/node/Heading.java +++ b/commonmark/src/main/java/org/commonmark/node/Heading.java @@ -2,6 +2,7 @@ /** * A heading, e.g.: + * *

      * First heading
      * =============
    @@ -10,7 +11,8 @@
      * 
    * * @see CommonMark Spec: ATX headings - * @see CommonMark Spec: Setext headings + * @see CommonMark Spec: Setext + * headings */ public class Heading extends Block { diff --git a/commonmark/src/main/java/org/commonmark/node/Image.java b/commonmark/src/main/java/org/commonmark/node/Image.java index 1b31f6020..62d71d88a 100644 --- a/commonmark/src/main/java/org/commonmark/node/Image.java +++ b/commonmark/src/main/java/org/commonmark/node/Image.java @@ -2,6 +2,7 @@ /** * An image, e.g.: + * *
      * ![foo](/url "title")
      * 
    @@ -13,8 +14,7 @@ public class Image extends Node { private String destination; private String title; - public Image() { - } + public Image() {} public Image(String destination, String title) { this.destination = destination; diff --git a/commonmark/src/main/java/org/commonmark/node/IndentedCodeBlock.java b/commonmark/src/main/java/org/commonmark/node/IndentedCodeBlock.java index 97642b7f3..25d01c29e 100644 --- a/commonmark/src/main/java/org/commonmark/node/IndentedCodeBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/IndentedCodeBlock.java @@ -2,12 +2,14 @@ /** * An indented code block, e.g.: + * *
    
      * Code follows:
      *
      *     foo
      *     bar
      * 
    + * *

    * * @see CommonMark Spec diff --git a/commonmark/src/main/java/org/commonmark/node/Link.java b/commonmark/src/main/java/org/commonmark/node/Link.java index 4edc7f676..f4390a151 100644 --- a/commonmark/src/main/java/org/commonmark/node/Link.java +++ b/commonmark/src/main/java/org/commonmark/node/Link.java @@ -2,21 +2,24 @@ /** * A link with a destination and an optional title; the link text is in child nodes. - *

    - * Example for an inline link in a CommonMark document: + * + *

    Example for an inline link in a CommonMark document: + * *

    
      * [link](/uri "title")
      * 
    - *

    - * The corresponding Link node would look like this: + * + *

    The corresponding Link node would look like this: + * *

      - *
    • {@link #getDestination()} returns {@code "/uri"} - *
    • {@link #getTitle()} returns {@code "title"} - *
    • A {@link Text} child node with {@link Text#getLiteral() getLiteral} that returns {@code "link"}
    • + *
    • {@link #getDestination()} returns {@code "/uri"} + *
    • {@link #getTitle()} returns {@code "title"} + *
    • A {@link Text} child node with {@link Text#getLiteral() getLiteral} that returns {@code + * "link"} *
    - *

    - * Note that the text in the link can contain inline formatting, so it could also contain an {@link Image} or - * {@link Emphasis}, etc. + * + *

    Note that the text in the link can contain inline formatting, so it could also contain an + * {@link Image} or {@link Emphasis}, etc. * * @see CommonMark Spec */ @@ -25,8 +28,7 @@ public class Link extends Node { private String destination; private String title; - public Link() { - } + public Link() {} public Link(String destination, String title) { this.destination = destination; diff --git a/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java b/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java index b866781f0..54321cb63 100644 --- a/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java +++ b/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java @@ -2,12 +2,13 @@ /** * A link reference definition, e.g.: + * *

    
      * [foo]: /url "title"
      * 
    - *

    - * They can be referenced anywhere else in the document to produce a link using [foo]. The definitions - * themselves are usually not rendered in the final output. + * + *

    They can be referenced anywhere else in the document to produce a link using [foo] + * . The definitions themselves are usually not rendered in the final output. * * @see CommonMark Spec */ @@ -17,8 +18,7 @@ public class LinkReferenceDefinition extends Block { private String destination; private String title; - public LinkReferenceDefinition() { - } + public LinkReferenceDefinition() {} public LinkReferenceDefinition(String label, String destination, String title) { this.label = label; diff --git a/commonmark/src/main/java/org/commonmark/node/ListBlock.java b/commonmark/src/main/java/org/commonmark/node/ListBlock.java index 1290bc622..d210a6b27 100644 --- a/commonmark/src/main/java/org/commonmark/node/ListBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/ListBlock.java @@ -1,8 +1,6 @@ package org.commonmark.node; -/** - * A list block like {@link BulletList} or {@link OrderedList}. - */ +/** A list block like {@link BulletList} or {@link OrderedList}. */ public abstract class ListBlock extends Block { private boolean tight; @@ -18,5 +16,4 @@ public boolean isTight() { public void setTight(boolean tight) { this.tight = tight; } - } diff --git a/commonmark/src/main/java/org/commonmark/node/ListItem.java b/commonmark/src/main/java/org/commonmark/node/ListItem.java index c4d1214e7..a53cbbe74 100644 --- a/commonmark/src/main/java/org/commonmark/node/ListItem.java +++ b/commonmark/src/main/java/org/commonmark/node/ListItem.java @@ -1,11 +1,12 @@ package org.commonmark.node; /** - * A child of a {@link ListBlock}, containing other blocks (e.g. {@link Paragraph}, other lists, etc). - *

    - * Note that a list item can't directly contain {@link Text}, it needs to be: - * {@link ListItem} : {@link Paragraph} : {@link Text}. - * If you want a list that is rendered tightly, create a list with {@link ListBlock#setTight(boolean)}. + * A child of a {@link ListBlock}, containing other blocks (e.g. {@link Paragraph}, other lists, + * etc). + * + *

    Note that a list item can't directly contain {@link Text}, it needs to be: {@link ListItem} : + * {@link Paragraph} : {@link Text}. If you want a list that is rendered tightly, create a list with + * {@link ListBlock#setTight(boolean)}. * * @see CommonMark Spec: List items */ @@ -20,15 +21,21 @@ public void accept(Visitor visitor) { } /** - * Returns the indent of the marker such as "-" or "1." in columns (spaces or tab stop of 4) if available, or null - * otherwise. - *

    - * Some examples and their marker indent: + * Returns the indent of the marker such as "-" or "1." in columns (spaces or tab stop of 4) if + * available, or null otherwise. + * + *

    Some examples and their marker indent: + * *

    - Foo
    + * * Marker indent: 0 + * *
     - Foo
    + * * Marker indent: 1 + * *
      1. Foo
    + * * Marker indent: 2 */ public Integer getMarkerIndent() { @@ -40,19 +47,26 @@ public void setMarkerIndent(Integer markerIndent) { } /** - * Returns the indent of the content in columns (spaces or tab stop of 4) if available, or null otherwise. - * The content indent is counted from the beginning of the line and includes the marker on the first line. - *

    - * Some examples and their content indent: + * Returns the indent of the content in columns (spaces or tab stop of 4) if available, or null + * otherwise. The content indent is counted from the beginning of the line and includes the + * marker on the first line. + * + *

    Some examples and their content indent: + * *

    - Foo
    + * * Content indent: 2 + * *
     - Foo
    + * * Content indent: 3 + * *
      1. Foo
    + * * Content indent: 5 - *

    - * Note that subsequent lines in the same list item need to be indented by at least the content indent to be counted - * as part of the list item. + * + *

    Note that subsequent lines in the same list item need to be indented by at least the + * content indent to be counted as part of the list item. */ public Integer getContentIndent() { return contentIndent; @@ -63,8 +77,8 @@ public void setContentIndent(Integer contentIndent) { } /** - * @deprecated list items should only contain block nodes; if you're trying to create a list that is rendered - * without paragraphs, use {@link ListBlock#setTight(boolean)} instead. + * @deprecated list items should only contain block nodes; if you're trying to create a list + * that is rendered without paragraphs, use {@link ListBlock#setTight(boolean)} instead. */ @Override @Deprecated diff --git a/commonmark/src/main/java/org/commonmark/node/Node.java b/commonmark/src/main/java/org/commonmark/node/Node.java index abbda67f2..adc64838c 100644 --- a/commonmark/src/main/java/org/commonmark/node/Node.java +++ b/commonmark/src/main/java/org/commonmark/node/Node.java @@ -6,8 +6,8 @@ /** * The base class of all CommonMark AST nodes ({@link Block} and inlines). - *

    - * A node can have multiple children, and a parent (except for the root node). + * + *

    A node can have multiple children, and a parent (except for the root node). */ public abstract class Node { @@ -85,9 +85,7 @@ public void unlink() { this.prev = null; } - /** - * Inserts the {@code sibling} node after {@code this} node. - */ + /** Inserts the {@code sibling} node after {@code this} node. */ public void insertAfter(Node sibling) { sibling.unlink(); sibling.next = this.next; @@ -102,9 +100,7 @@ public void insertAfter(Node sibling) { } } - /** - * Inserts the {@code sibling} node before {@code this} node. - */ + /** Inserts the {@code sibling} node before {@code this} node. */ public void insertBefore(Node sibling) { sibling.unlink(); sibling.prev = this.prev; diff --git a/commonmark/src/main/java/org/commonmark/node/Nodes.java b/commonmark/src/main/java/org/commonmark/node/Nodes.java index 22d5932af..71e0925db 100644 --- a/commonmark/src/main/java/org/commonmark/node/Nodes.java +++ b/commonmark/src/main/java/org/commonmark/node/Nodes.java @@ -9,12 +9,9 @@ */ public class Nodes { - private Nodes() { - } + private Nodes() {} - /** - * The nodes between (not including) start and end. - */ + /** The nodes between (not including) start and end. */ public static Iterable between(Node start, Node end) { return new NodeIterable(start.getNext(), end); } @@ -63,4 +60,3 @@ public void remove() { } } } - diff --git a/commonmark/src/main/java/org/commonmark/node/OrderedList.java b/commonmark/src/main/java/org/commonmark/node/OrderedList.java index 61f8902c0..70cfd62c2 100644 --- a/commonmark/src/main/java/org/commonmark/node/OrderedList.java +++ b/commonmark/src/main/java/org/commonmark/node/OrderedList.java @@ -2,13 +2,14 @@ /** * An ordered list, e.g.: + * *

    
      * 1. One
      * 2. Two
      * 3. Three
      * 
    - *

    - * The children are {@link ListItem} blocks, which contain other blocks (or nested lists). + * + *

    The children are {@link ListItem} blocks, which contain other blocks (or nested lists). * * @see CommonMark Spec: List items */ @@ -34,7 +35,8 @@ public void setMarkerStartNumber(Integer markerStartNumber) { } /** - * @return the delimiter used in the marker, e.g. {@code .} or {@code )}, if available, or null otherwise + * @return the delimiter used in the marker, e.g. {@code .} or {@code )}, if available, or null + * otherwise */ public String getMarkerDelimiter() { return markerDelimiter; @@ -65,7 +67,9 @@ public void setStartNumber(int startNumber) { */ @Deprecated public char getDelimiter() { - return markerDelimiter != null && !markerDelimiter.isEmpty() ? markerDelimiter.charAt(0) : '\0'; + return markerDelimiter != null && !markerDelimiter.isEmpty() + ? markerDelimiter.charAt(0) + : '\0'; } /** diff --git a/commonmark/src/main/java/org/commonmark/node/SoftLineBreak.java b/commonmark/src/main/java/org/commonmark/node/SoftLineBreak.java index 87445db56..21ba09411 100644 --- a/commonmark/src/main/java/org/commonmark/node/SoftLineBreak.java +++ b/commonmark/src/main/java/org/commonmark/node/SoftLineBreak.java @@ -2,6 +2,7 @@ /** * A soft line break (as opposed to a {@link HardLineBreak}), e.g. between: + * *

      * foo
      * bar
    diff --git a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java
    index 6558cc84a..ab797cb18 100644
    --- a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java
    +++ b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java
    @@ -4,22 +4,26 @@
     
     /**
      * A source span references a snippet of text from the source input.
    - * 

    - * It has a starting position (line and column index) and a length of how many characters it spans. - *

    - * For example, this CommonMark source text: + * + *

    It has a starting position (line and column index) and a length of how many characters it + * spans. + * + *

    For example, this CommonMark source text: + * *

    
      * > foo
      * 
    + * * The {@link BlockQuote} node would have this source span: line 0, column 0, length 5. - *

    - * The {@link Paragraph} node inside it would have: line 0, column 2, length 3. - *

    - * If a block has multiple lines, it will have a source span for each line. - *

    - * Note that the column index and length are measured in Java characters (UTF-16 code units). If you're outputting them - * to be consumed by another programming language, e.g. one that uses UTF-8 strings, you will need to translate them, - * otherwise characters such as emojis will result in incorrect positions. + * + *

    The {@link Paragraph} node inside it would have: line 0, column 2, length 3. + * + *

    If a block has multiple lines, it will have a source span for each line. + * + *

    Note that the column index and length are measured in Java characters (UTF-16 code units). If + * you're outputting them to be consumed by another programming language, e.g. one that uses UTF-8 + * strings, you will need to translate them, otherwise characters such as emojis will result in + * incorrect positions. * * @since 0.16.0 */ @@ -35,8 +39,8 @@ public static SourceSpan of(int line, int col, int input, int length) { } /** - * @deprecated Use {{@link #of(int, int, int, int)}} instead to also specify input index. Using the deprecated one - * will set {@link #inputIndex} to 0. + * @deprecated Use {{@link #of(int, int, int, int)}} instead to also specify input index. Using + * the deprecated one will set {@link #inputIndex} to 0. */ @Deprecated public static SourceSpan of(int lineIndex, int columnIndex, int length) { @@ -70,8 +74,8 @@ public int getLineIndex() { } /** - * @return 0-based index of column (character on line) in source, e.g. 0 for the first character of a line, 1 for - * the second character, etc + * @return 0-based index of column (character on line) in source, e.g. 0 for the first character + * of a line, 1 for the second character, etc */ public int getColumnIndex() { return columnIndex; @@ -101,21 +105,28 @@ public SourceSpan subSpan(int beginIndex, int endIndex) { throw new IndexOutOfBoundsException("beginIndex " + beginIndex + " + must be >= 0"); } if (beginIndex > length) { - throw new IndexOutOfBoundsException("beginIndex " + beginIndex + " must be <= length " + length); + throw new IndexOutOfBoundsException( + "beginIndex " + beginIndex + " must be <= length " + length); } if (endIndex < 0) { throw new IndexOutOfBoundsException("endIndex " + endIndex + " + must be >= 0"); } if (endIndex > length) { - throw new IndexOutOfBoundsException("endIndex " + endIndex + " must be <= length " + length); + throw new IndexOutOfBoundsException( + "endIndex " + endIndex + " must be <= length " + length); } if (beginIndex > endIndex) { - throw new IndexOutOfBoundsException("beginIndex " + beginIndex + " must be <= endIndex " + endIndex); + throw new IndexOutOfBoundsException( + "beginIndex " + beginIndex + " must be <= endIndex " + endIndex); } if (beginIndex == 0 && endIndex == length) { return this; } - return new SourceSpan(lineIndex, columnIndex + beginIndex, inputIndex + beginIndex, endIndex - beginIndex); + return new SourceSpan( + lineIndex, + columnIndex + beginIndex, + inputIndex + beginIndex, + endIndex - beginIndex); } @Override @@ -127,10 +138,10 @@ public boolean equals(Object o) { return false; } SourceSpan that = (SourceSpan) o; - return lineIndex == that.lineIndex && - columnIndex == that.columnIndex && - inputIndex == that.inputIndex && - length == that.length; + return lineIndex == that.lineIndex + && columnIndex == that.columnIndex + && inputIndex == that.inputIndex + && length == that.length; } @Override @@ -140,11 +151,15 @@ public int hashCode() { @Override public String toString() { - return "SourceSpan{" + - "line=" + lineIndex + - ", column=" + columnIndex + - ", input=" + inputIndex + - ", length=" + length + - "}"; + return "SourceSpan{" + + "line=" + + lineIndex + + ", column=" + + columnIndex + + ", input=" + + inputIndex + + ", length=" + + length + + "}"; } } diff --git a/commonmark/src/main/java/org/commonmark/node/SourceSpans.java b/commonmark/src/main/java/org/commonmark/node/SourceSpans.java index 975d7fbdb..945f1c19a 100644 --- a/commonmark/src/main/java/org/commonmark/node/SourceSpans.java +++ b/commonmark/src/main/java/org/commonmark/node/SourceSpans.java @@ -42,7 +42,13 @@ public void addAll(List other) { SourceSpan a = sourceSpans.get(lastIndex); SourceSpan b = other.get(0); if (a.getInputIndex() + a.getLength() == b.getInputIndex()) { - sourceSpans.set(lastIndex, SourceSpan.of(a.getLineIndex(), a.getColumnIndex(), a.getInputIndex(), a.getLength() + b.getLength())); + sourceSpans.set( + lastIndex, + SourceSpan.of( + a.getLineIndex(), + a.getColumnIndex(), + a.getInputIndex(), + a.getLength() + b.getLength())); sourceSpans.addAll(other.subList(1, other.size())); } else { sourceSpans.addAll(other); diff --git a/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java b/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java index 0dbeed3df..f09786f84 100644 --- a/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java +++ b/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java @@ -2,18 +2,19 @@ /** * Strong emphasis, e.g.: + * *

    
      * Some **strong emphasis** or __strong emphasis__
      * 
    * - * @see CommonMark Spec: Emphasis and strong emphasis + * @see CommonMark Spec: + * Emphasis and strong emphasis */ public class StrongEmphasis extends Node implements Delimited { private String delimiter; - public StrongEmphasis() { - } + public StrongEmphasis() {} public StrongEmphasis(String delimiter) { this.delimiter = delimiter; diff --git a/commonmark/src/main/java/org/commonmark/node/Text.java b/commonmark/src/main/java/org/commonmark/node/Text.java index 9a04c41c1..5367f2f3f 100644 --- a/commonmark/src/main/java/org/commonmark/node/Text.java +++ b/commonmark/src/main/java/org/commonmark/node/Text.java @@ -2,11 +2,13 @@ /** * A text node, e.g. in: + * *
      * foo *bar*
      * 
    - *

    - * The foo is a text node, and the bar inside the emphasis is also a text node. + * + *

    The foo is a text node, and the bar inside the emphasis is also a + * text node. * * @see CommonMark Spec */ @@ -14,8 +16,7 @@ public class Text extends Node { private String literal; - public Text() { - } + public Text() {} public Text(String literal) { this.literal = literal; diff --git a/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java b/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java index a31131e07..e2391f57d 100644 --- a/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java +++ b/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java @@ -2,6 +2,7 @@ /** * A thematic break, e.g. between text: + * *

      * Some text
      *
    diff --git a/commonmark/src/main/java/org/commonmark/node/Visitor.java b/commonmark/src/main/java/org/commonmark/node/Visitor.java
    index a155296f0..0e3fc9f84 100644
    --- a/commonmark/src/main/java/org/commonmark/node/Visitor.java
    +++ b/commonmark/src/main/java/org/commonmark/node/Visitor.java
    @@ -2,8 +2,8 @@
     
     /**
      * Node visitor.
    - * 

    - * Implementations should subclass {@link AbstractVisitor} instead of implementing this directly. + * + *

    Implementations should subclass {@link AbstractVisitor} instead of implementing this directly. */ public interface Visitor { diff --git a/commonmark/src/main/java/org/commonmark/node/package-info.java b/commonmark/src/main/java/org/commonmark/node/package-info.java index e9fee1aba..3408d2e72 100644 --- a/commonmark/src/main/java/org/commonmark/node/package-info.java +++ b/commonmark/src/main/java/org/commonmark/node/package-info.java @@ -1,4 +1,5 @@ /** - * AST node types (see {@link org.commonmark.node.Node}) and visitors (see {@link org.commonmark.node.AbstractVisitor}) + * AST node types (see {@link org.commonmark.node.Node}) and visitors (see {@link + * org.commonmark.node.AbstractVisitor}) */ package org.commonmark.node; diff --git a/commonmark/src/main/java/org/commonmark/package-info.java b/commonmark/src/main/java/org/commonmark/package-info.java index b683017f6..9ff3301e9 100644 --- a/commonmark/src/main/java/org/commonmark/package-info.java +++ b/commonmark/src/main/java/org/commonmark/package-info.java @@ -1,10 +1,11 @@ /** * Root package of commonmark-java + * *

      - *
    • {@link org.commonmark.parser} for parsing input text to AST nodes
    • - *
    • {@link org.commonmark.node} for AST node types and visitors
    • - *
    • {@link org.commonmark.renderer.html} for HTML rendering
    • - *
    • {@link org.commonmark.renderer.markdown} for Markdown rendering
    • + *
    • {@link org.commonmark.parser} for parsing input text to AST nodes + *
    • {@link org.commonmark.node} for AST node types and visitors + *
    • {@link org.commonmark.renderer.html} for HTML rendering + *
    • {@link org.commonmark.renderer.markdown} for Markdown rendering *
    */ package org.commonmark; diff --git a/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java b/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java index 91d2b4e00..f53378c2f 100644 --- a/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java +++ b/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java @@ -1,22 +1,16 @@ package org.commonmark.parser; /** - * Whether to include {@link org.commonmark.node.SourceSpan} or not while parsing, - * see {@link Parser.Builder#includeSourceSpans(IncludeSourceSpans)}. + * Whether to include {@link org.commonmark.node.SourceSpan} or not while parsing, see {@link + * Parser.Builder#includeSourceSpans(IncludeSourceSpans)}. * * @since 0.16.0 */ public enum IncludeSourceSpans { - /** - * Do not include source spans. - */ + /** Do not include source spans. */ NONE, - /** - * Include source spans on {@link org.commonmark.node.Block} nodes. - */ + /** Include source spans on {@link org.commonmark.node.Block} nodes. */ BLOCKS, - /** - * Include source spans on block nodes and inline nodes. - */ + /** Include source spans on block nodes and inline nodes. */ BLOCKS_AND_INLINES, } diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java index 49043a64f..1c6714686 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java @@ -2,9 +2,7 @@ import org.commonmark.node.Node; -/** - * Parser for inline content (text, links, emphasized text, etc). - */ +/** Parser for inline content (text, links, emphasized text, etc). */ public interface InlineParser { /** diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java index 12007610b..774773273 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -1,32 +1,30 @@ package org.commonmark.parser; +import java.util.List; +import java.util.Set; import org.commonmark.node.LinkReferenceDefinition; -import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.beta.InlineContentParserFactory; +import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.delimiter.DelimiterProcessor; -import java.util.List; -import java.util.Set; - -/** - * Context for inline parsing. - */ +/** Context for inline parsing. */ public interface InlineParserContext { /** - * @return custom inline content parsers that have been configured with - * {@link Parser.Builder#customInlineContentParserFactory(InlineContentParserFactory)} + * @return custom inline content parsers that have been configured with {@link + * Parser.Builder#customInlineContentParserFactory(InlineContentParserFactory)} */ List getCustomInlineContentParserFactories(); /** - * @return custom delimiter processors that have been configured with - * {@link Parser.Builder#customDelimiterProcessor(DelimiterProcessor)} + * @return custom delimiter processors that have been configured with {@link + * Parser.Builder#customDelimiterProcessor(DelimiterProcessor)} */ List getCustomDelimiterProcessors(); /** - * @return custom link processors that have been configured with {@link Parser.Builder#linkProcessor}. + * @return custom link processors that have been configured with {@link + * Parser.Builder#linkProcessor}. */ List getCustomLinkProcessors(); @@ -37,9 +35,9 @@ public interface InlineParserContext { /** * Look up a {@link LinkReferenceDefinition} for a given label. - *

    - * Note that the passed in label does not need to be normalized; implementations are responsible for doing the - * normalization before lookup. + * + *

    Note that the passed in label does not need to be normalized; implementations are + * responsible for doing the normalization before lookup. * * @param label the link label to look up * @return the definition if one exists, {@code null} otherwise @@ -50,9 +48,9 @@ public interface InlineParserContext { /** * Look up a definition of a type for a given label. - *

    - * Note that the passed in label does not need to be normalized; implementations are responsible for doing the - * normalization before lookup. + * + *

    Note that the passed in label does not need to be normalized; implementations are + * responsible for doing the normalization before lookup. * * @return the definition if one exists, null otherwise */ diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java index c1640e9d8..0eaac3c30 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java @@ -1,12 +1,11 @@ package org.commonmark.parser; -/** - * Factory for custom inline parser. - */ +/** Factory for custom inline parser. */ public interface InlineParserFactory { /** - * Create an {@link InlineParser} to use for parsing inlines. This is called once per parsed document. + * Create an {@link InlineParser} to use for parsing inlines. This is called once per parsed + * document. */ InlineParser create(InlineParserContext inlineParserContext); } diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 8faac789b..23fd75f68 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -1,27 +1,26 @@ package org.commonmark.parser; +import java.io.IOException; +import java.io.Reader; +import java.util.*; import org.commonmark.Extension; import org.commonmark.internal.Definitions; import org.commonmark.internal.DocumentParser; import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; import org.commonmark.node.*; +import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.beta.LinkInfo; import org.commonmark.parser.beta.LinkProcessor; -import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.parser.beta.LinkResult; import org.commonmark.parser.block.BlockParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; -import java.io.IOException; -import java.io.Reader; -import java.util.*; - - /** * Parses input text to a tree of nodes. - *

    - * Start with the {@link #builder} method, configure the parser and build it. Example: + * + *

    Start with the {@link #builder} method, configure the parser and build it. Example: + * *

    
      * Parser parser = Parser.builder().build();
      * Node document = parser.parse("input text");
    @@ -40,7 +39,9 @@ public class Parser {
         private final int maxOpenBlockParsers;
     
         private Parser(Builder builder) {
    -        this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes);
    +        this.blockParserFactories =
    +                DocumentParser.calculateBlockParserFactories(
    +                        builder.blockParserFactories, builder.enabledBlockTypes);
             this.inlineParserFactory = builder.getInlineParserFactory();
             this.postProcessors = builder.postProcessors;
             this.inlineContentParserFactories = builder.inlineContentParserFactories;
    @@ -50,10 +51,15 @@ private Parser(Builder builder) {
             this.includeSourceSpans = builder.includeSourceSpans;
             this.maxOpenBlockParsers = builder.maxOpenBlockParsers;
     
    -        // Try to construct an inline parser. Invalid configuration might result in an exception, which we want to
    -        // detect as soon as possible.
    -        var context = new InlineParserContextImpl(
    -                inlineContentParserFactories, delimiterProcessors, linkProcessors, linkMarkers, new Definitions());
    +        // Try to construct an inline parser. Invalid configuration might result in an exception,
    +        // which we want to detect as soon as possible.
    +        var context =
    +                new InlineParserContextImpl(
    +                        inlineContentParserFactories,
    +                        delimiterProcessors,
    +                        linkProcessors,
    +                        linkMarkers,
    +                        new Definitions());
             this.inlineParserFactory.create(context);
         }
     
    @@ -68,8 +74,8 @@ public static Builder builder() {
     
         /**
          * Parse the specified input text into a tree of nodes.
    -     * 

    - * This method is thread-safe (a new parser state is used for each invocation). + * + *

    This method is thread-safe (a new parser state is used for each invocation). * * @param input the text to parse - must not be null * @return the root node @@ -82,7 +88,9 @@ public Node parse(String input) { } /** - * Parse the specified reader into a tree of nodes. The caller is responsible for closing the reader. + * Parse the specified reader into a tree of nodes. The caller is responsible for closing the + * reader. + * *

    
          * Parser parser = Parser.builder().build();
          * try (InputStreamReader reader = new InputStreamReader(new FileInputStream("file.md"), StandardCharsets.UTF_8)) {
    @@ -90,10 +98,12 @@ public Node parse(String input) {
          *     // ...
          * }
          * 
    - * Note that if you have a file with a byte order mark (BOM), you need to skip it before handing the reader to this - * library. There's existing classes that do that, e.g. see {@code BOMInputStream} in Commons IO. - *

    - * This method is thread-safe (a new parser state is used for each invocation). + * + * Note that if you have a file with a byte order mark (BOM), you need to skip it before handing + * the reader to this library. There's existing classes that do that, e.g. see {@code + * BOMInputStream} in Commons IO. + * + *

    This method is thread-safe (a new parser state is used for each invocation). * * @param input the reader to parse - must not be null * @return the root node @@ -107,8 +117,15 @@ public Node parseReader(Reader input) throws IOException { } private DocumentParser createDocumentParser() { - return new DocumentParser(blockParserFactories, inlineParserFactory, inlineContentParserFactories, - delimiterProcessors, linkProcessors, linkMarkers, includeSourceSpans, maxOpenBlockParsers); + return new DocumentParser( + blockParserFactories, + inlineParserFactory, + inlineContentParserFactories, + delimiterProcessors, + linkProcessors, + linkMarkers, + includeSourceSpans, + maxOpenBlockParsers); } private Node postProcess(Node document) { @@ -118,17 +135,17 @@ private Node postProcess(Node document) { return document; } - /** - * Builder for configuring a {@link Parser}. - */ + /** Builder for configuring a {@link Parser}. */ public static class Builder { private final List blockParserFactories = new ArrayList<>(); - private final List inlineContentParserFactories = new ArrayList<>(); + private final List inlineContentParserFactories = + new ArrayList<>(); private final List delimiterProcessors = new ArrayList<>(); private final List linkProcessors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); private final Set linkMarkers = new HashSet<>(); - private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); + private Set> enabledBlockTypes = + DocumentParser.getDefaultBlockParserTypes(); private InlineParserFactory inlineParserFactory; private IncludeSourceSpans includeSourceSpans = IncludeSourceSpans.NONE; private int maxOpenBlockParsers = Integer.MAX_VALUE; @@ -157,29 +174,31 @@ public Builder extensions(Iterable extensions) { /** * Describe the list of markdown features the parser will recognize and parse. - *

    - * By default, CommonMark will recognize and parse the following set of "block" elements: + * + *

    By default, CommonMark will recognize and parse the following set of "block" elements: + * *

      - *
    • {@link Heading} ({@code #}) - *
    • {@link HtmlBlock} ({@code }) - *
    • {@link ThematicBreak} (Horizontal Rule) ({@code ---}) - *
    • {@link FencedCodeBlock} ({@code ```}) - *
    • {@link IndentedCodeBlock} - *
    • {@link BlockQuote} ({@code >}) - *
    • {@link ListBlock} (Ordered / Unordered List) ({@code 1. / *}) + *
    • {@link Heading} ({@code #}) + *
    • {@link HtmlBlock} ({@code }) + *
    • {@link ThematicBreak} (Horizontal Rule) ({@code ---}) + *
    • {@link FencedCodeBlock} ({@code ```}) + *
    • {@link IndentedCodeBlock} + *
    • {@link BlockQuote} ({@code >}) + *
    • {@link ListBlock} (Ordered / Unordered List) ({@code 1. / *}) *
    - *

    - * To parse only a subset of the features listed above, pass a list of each feature's associated {@link Block} class. - *

    - * E.g., to only parse headings and lists: - *

    -         *     {@code
    -         *     Parser.builder().enabledBlockTypes(Set.of(Heading.class, ListBlock.class));
    -         *     }
    -         * 
    * - * @param enabledBlockTypes A list of block nodes the parser will parse. - * If this list is empty, the parser will not recognize any CommonMark core features. + *

    To parse only a subset of the features listed above, pass a list of each feature's + * associated {@link Block} class. + * + *

    E.g., to only parse headings and lists: + * + *

    {@code
    +         * Parser.builder().enabledBlockTypes(Set.of(Heading.class, ListBlock.class));
    +         *
    +         * }
    + * + * @param enabledBlockTypes A list of block nodes the parser will parse. If this list is + * empty, the parser will not recognize any CommonMark core features. * @return {@code this} */ public Builder enabledBlockTypes(Set> enabledBlockTypes) { @@ -190,9 +209,10 @@ public Builder enabledBlockTypes(Set> enabledBlockTypes) } /** - * Whether to calculate source positions for parsed {@link Node Nodes}, see {@link Node#getSourceSpans()}. - *

    - * By default, source spans are disabled. + * Whether to calculate source positions for parsed {@link Node Nodes}, see {@link + * Node#getSourceSpans()}. + * + *

    By default, source spans are disabled. * * @param includeSourceSpans which kind of source spans should be included * @return {@code this} @@ -205,12 +225,12 @@ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { /** * Limit how many block parsers may be open at once while parsing. - *

    - * Once the limit is reached, additional block starts are treated as plain text instead of - * creating deeper nested block structure. - *

    - * The document root parser is not counted. The default is unlimited, so callers that keep - * using {@code Parser.builder().build()} preserve behavior. + * + *

    Once the limit is reached, additional block starts are treated as plain text instead + * of creating deeper nested block structure. + * + *

    The document root parser is not counted. The default is unlimited, so callers that + * keep using {@code Parser.builder().build()} preserve behavior. * * @param maxOpenBlockParsers maximum number of open non-document block parsers, must be * zero or greater @@ -226,10 +246,10 @@ public Builder maxOpenBlockParsers(int maxOpenBlockParsers) { /** * Add a custom block parser factory. - *

    - * Note that custom factories are applied before the built-in factories. This is so that - * extensions can change how some syntax is parsed that would otherwise be handled by built-in factories. - * "With great power comes great responsibility." + * + *

    Note that custom factories are applied before the built-in factories. This is + * so that extensions can change how some syntax is parsed that would otherwise be handled + * by built-in factories. "With great power comes great responsibility." * * @param blockParserFactory a block parser factory implementation * @return {@code this} @@ -241,28 +261,33 @@ public Builder customBlockParserFactory(BlockParserFactory blockParserFactory) { } /** - * Add a factory for a custom inline content parser, for extending inline parsing or overriding built-in parsing. - *

    - * Note that parsers are triggered based on a special character as specified by - * {@link InlineContentParserFactory#getTriggerCharacters()}. It is possible to register multiple parsers for the same - * character, or even for some built-in special character such as {@code `}. The custom parsers are tried first - * in order in which they are registered, and then the built-in ones. + * Add a factory for a custom inline content parser, for extending inline parsing or + * overriding built-in parsing. + * + *

    Note that parsers are triggered based on a special character as specified by {@link + * InlineContentParserFactory#getTriggerCharacters()}. It is possible to register multiple + * parsers for the same character, or even for some built-in special character such as + * {@code `}. The custom parsers are tried first in order in which they are registered, and + * then the built-in ones. */ - public Builder customInlineContentParserFactory(InlineContentParserFactory inlineContentParserFactory) { - Objects.requireNonNull(inlineContentParserFactory, "inlineContentParser must not be null"); + public Builder customInlineContentParserFactory( + InlineContentParserFactory inlineContentParserFactory) { + Objects.requireNonNull( + inlineContentParserFactory, "inlineContentParser must not be null"); inlineContentParserFactories.add(inlineContentParserFactory); return this; } /** * Add a custom delimiter processor for inline parsing. - *

    - * Note that multiple delimiter processors with the same characters can be added, as long as they have a - * different minimum length. In that case, the processor with the shortest matching length is used. Adding more - * than one delimiter processor with the same character and minimum length is invalid. - *

    - * If you want more control over how parsing is done, you might want to use - * {@link #customInlineContentParserFactory} instead. + * + *

    Note that multiple delimiter processors with the same characters can be added, as long + * as they have a different minimum length. In that case, the processor with the shortest + * matching length is used. Adding more than one delimiter processor with the same character + * and minimum length is invalid. + * + *

    If you want more control over how parsing is done, you might want to use {@link + * #customInlineContentParserFactory} instead. * * @param delimiterProcessor a delimiter processor implementation * @return {@code this} @@ -275,9 +300,10 @@ public Builder customDelimiterProcessor(DelimiterProcessor delimiterProcessor) { /** * Add a custom link/image processor for inline parsing. - *

    - * Multiple link processors can be added, and will be tried in order in which they were added. If no link - * processor applies, the normal behavior applies. That means these can override built-in link parsing. + * + *

    Multiple link processors can be added, and will be tried in order in which they were + * added. If no link processor applies, the normal behavior applies. That means these can + * override built-in link parsing. * * @param linkProcessor a link processor implementation * @return {@code this} @@ -289,12 +315,14 @@ public Builder linkProcessor(LinkProcessor linkProcessor) { } /** - * Add a custom link marker for link processing. A link marker is a character like {@code !} which, if it - * appears before the {@code [} of a link, changes the meaning of the link. - *

    - * If a link marker followed by a valid link is parsed, the {@link org.commonmark.parser.beta.LinkInfo} - * that is passed to {@link LinkProcessor} will have its {@link LinkInfo#marker()} set. A link processor should - * check the {@link Text#getLiteral()} and then do any processing, and will probably want to use {@link LinkResult#includeMarker()}. + * Add a custom link marker for link processing. A link marker is a character like {@code !} + * which, if it appears before the {@code [} of a link, changes the meaning of the link. + * + *

    If a link marker followed by a valid link is parsed, the {@link + * org.commonmark.parser.beta.LinkInfo} that is passed to {@link LinkProcessor} will have + * its {@link LinkInfo#marker()} set. A link processor should check the {@link + * Text#getLiteral()} and then do any processing, and will probably want to use {@link + * LinkResult#includeMarker()}. * * @param linkMarker a link marker character * @return {@code this} @@ -313,18 +341,13 @@ public Builder postProcessor(PostProcessor postProcessor) { /** * Overrides the parser used for inline markdown processing. - *

    - * Provide an implementation of InlineParserFactory which provides a custom inline parser - * to modify how the following are parsed: - * bold (**) - * italic (*) - * strikethrough (~~) - * backtick quote (`) - * link ([title](http://)) - * image (![alt](http://)) - *

    - * Note that if this method is not called or the inline parser factory is set to null, then the default - * implementation will be used. + * + *

    Provide an implementation of InlineParserFactory which provides a custom inline parser + * to modify how the following are parsed: bold (**) italic (*) strikethrough (~~) backtick + * quote (`) link ([title](http://)) image (![alt](http://)) + * + *

    Note that if this method is not called or the inline parser factory is set to null, + * then the default implementation will be used. * * @param inlineParserFactory an inline parser factory implementation * @return {@code this} @@ -343,9 +366,7 @@ private InlineParserFactory getInlineParserFactory() { } } - /** - * Extension for {@link Parser}. - */ + /** Extension for {@link Parser}. */ public interface ParserExtension extends Extension { void extend(Builder parserBuilder); } diff --git a/commonmark/src/main/java/org/commonmark/parser/PostProcessor.java b/commonmark/src/main/java/org/commonmark/parser/PostProcessor.java index be00347ca..ffa79370a 100644 --- a/commonmark/src/main/java/org/commonmark/parser/PostProcessor.java +++ b/commonmark/src/main/java/org/commonmark/parser/PostProcessor.java @@ -9,5 +9,4 @@ public interface PostProcessor { * @return the result of post-processing, may be a modified {@code node} argument */ Node process(Node node); - } diff --git a/commonmark/src/main/java/org/commonmark/parser/SourceLine.java b/commonmark/src/main/java/org/commonmark/parser/SourceLine.java index 92a8cdfaf..da7fe71f4 100644 --- a/commonmark/src/main/java/org/commonmark/parser/SourceLine.java +++ b/commonmark/src/main/java/org/commonmark/parser/SourceLine.java @@ -1,8 +1,7 @@ package org.commonmark.parser; -import org.commonmark.node.SourceSpan; - import java.util.Objects; +import org.commonmark.node.SourceSpan; /** * A line or part of a line from the input source. @@ -39,7 +38,8 @@ public SourceLine substring(int beginIndex, int endIndex) { if (length != 0) { int columnIndex = sourceSpan.getColumnIndex() + beginIndex; int inputIndex = sourceSpan.getInputIndex() + beginIndex; - newSourceSpan = SourceSpan.of(sourceSpan.getLineIndex(), columnIndex, inputIndex, length); + newSourceSpan = + SourceSpan.of(sourceSpan.getLineIndex(), columnIndex, inputIndex, length); } } return SourceLine.of(newContent, newSourceSpan); diff --git a/commonmark/src/main/java/org/commonmark/parser/SourceLines.java b/commonmark/src/main/java/org/commonmark/parser/SourceLines.java index 0b4290341..b21236281 100644 --- a/commonmark/src/main/java/org/commonmark/parser/SourceLines.java +++ b/commonmark/src/main/java/org/commonmark/parser/SourceLines.java @@ -1,9 +1,8 @@ package org.commonmark.parser; -import org.commonmark.node.SourceSpan; - import java.util.ArrayList; import java.util.List; +import org.commonmark.node.SourceSpan; /** * A set of lines ({@link SourceLine}) from the input source. diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParser.java b/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParser.java index bc5c9a54f..83941e76c 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParser.java @@ -1,21 +1,23 @@ package org.commonmark.parser.beta; /** - * Parser for a type of inline content. Registered via a {@link InlineContentParserFactory} and created by its - * {@link InlineContentParserFactory#create() create} method. The lifetime of this is tied to each inline content - * snippet that is parsed, as a new instance is created for each. + * Parser for a type of inline content. Registered via a {@link InlineContentParserFactory} and + * created by its {@link InlineContentParserFactory#create() create} method. The lifetime of this is + * tied to each inline content snippet that is parsed, as a new instance is created for each. */ public interface InlineContentParser { /** - * Try to parse inline content starting from the current position. Note that the character at the current position - * is one of {@link InlineContentParserFactory#getTriggerCharacters()} of the factory that created this parser. - *

    - * For a given inline content snippet that is being parsed, this method can be called multiple times: each time a - * trigger character is encountered. + * Try to parse inline content starting from the current position. Note that the character at + * the current position is one of {@link InlineContentParserFactory#getTriggerCharacters()} of + * the factory that created this parser. + * + *

    For a given inline content snippet that is being parsed, this method can be called + * multiple times: each time a trigger character is encountered. * * @param inlineParserState the current state of the inline parser - * @return the result of parsing; can indicate that this parser is not interested, or that parsing was successful + * @return the result of parsing; can indicate that this parser is not interested, or that + * parsing was successful */ ParsedInline tryParse(InlineParserState inlineParserState); } diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParserFactory.java b/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParserFactory.java index c86f93a41..c52abab2a 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParserFactory.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/InlineContentParserFactory.java @@ -4,21 +4,24 @@ /** * A factory for extending inline content parsing. - *

    - * See {@link org.commonmark.parser.Parser.Builder#customInlineContentParserFactory} for how to register it. + * + *

    See {@link org.commonmark.parser.Parser.Builder#customInlineContentParserFactory} for how to + * register it. */ public interface InlineContentParserFactory { /** - * An inline content parser needs to have a special "trigger" character which activates it. When this character is - * encountered during inline parsing, {@link InlineContentParser#tryParse} is called with the current parser state. - * It can also register for more than one trigger character. + * An inline content parser needs to have a special "trigger" character which activates it. When + * this character is encountered during inline parsing, {@link InlineContentParser#tryParse} is + * called with the current parser state. It can also register for more than one trigger + * character. */ Set getTriggerCharacters(); /** - * Create an {@link InlineContentParser} that will do the parsing. Create is called once per text snippet of inline - * content inside block structures, and then called each time a trigger character is encountered. + * Create an {@link InlineContentParser} that will do the parsing. Create is called once per + * text snippet of inline content inside block structures, and then called each time a trigger + * character is encountered. */ InlineContentParser create(); } diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/InlineParserState.java b/commonmark/src/main/java/org/commonmark/parser/beta/InlineParserState.java index e434d45d6..246aec043 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/InlineParserState.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/InlineParserState.java @@ -3,10 +3,10 @@ public interface InlineParserState { /** - * Return a scanner for the input for the current position (on the trigger character that the inline parser was - * added for). - *

    - * Note that this always returns the same instance, if you want to backtrack you need to use + * Return a scanner for the input for the current position (on the trigger character that the + * inline parser was added for). + * + *

    Note that this always returns the same instance, if you want to backtrack you need to use * {@link Scanner#position()} and {@link Scanner#setPosition(Position)}. */ Scanner scanner(); diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java index b2fda57e4..210ca9ddb 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkInfo.java @@ -4,62 +4,64 @@ /** * A parsed link/image. There are different types of links. - *

    - * Inline links: + * + *

    Inline links: + * *

      * [text](destination)
      * [text](destination "title")
      * 
    - *

    - * Reference links, which have different subtypes. Full:: + * + *

    Reference links, which have different subtypes. Full:: + * *

      * [text][label]
      * 
    + * * Collapsed (label is ""): + * *
      * [text][]
      * 
    + * * Shortcut (label is null): + * *
      * [text]
      * 
    - * Images use the same syntax as links but with a {@code !} {@link #marker()} front, e.g. {@code ![text](destination)}. + * + * Images use the same syntax as links but with a {@code !} {@link #marker()} front, e.g. {@code + * ![text](destination)}. */ public interface LinkInfo { /** - * The marker if present, or null. A marker is e.g. {@code !} for an image, or a custom marker as specified in - * {@link org.commonmark.parser.Parser.Builder#linkMarker}. + * The marker if present, or null. A marker is e.g. {@code !} for an image, or a custom marker + * as specified in {@link org.commonmark.parser.Parser.Builder#linkMarker}. */ Text marker(); - /** - * The text node of the opening bracket {@code [}. - */ + /** The text node of the opening bracket {@code [}. */ Text openingBracket(); - /** - * The text between the first brackets, e.g. `foo` in `[foo][bar]`. - */ + /** The text between the first brackets, e.g. `foo` in `[foo][bar]`. */ String text(); /** - * The label, or null for inline links or for shortcut links (in which case {@link #text()} should be used as the label). + * The label, or null for inline links or for shortcut links (in which case {@link #text()} + * should be used as the label). */ String label(); - /** - * The destination if available, e.g. in `[foo](destination)`, or null - */ + /** The destination if available, e.g. in `[foo](destination)`, or null */ String destination(); - /** - * The title if available, e.g. in `[foo](destination "title")`, or null - */ + /** The title if available, e.g. in `[foo](destination "title")`, or null */ String title(); /** * The position after the closing text bracket, e.g.: + * *
          * [foo][bar]
          *      ^
    diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java
    index 3e448fd91..595e8d73d 100644
    --- a/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java
    +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkProcessor.java
    @@ -4,37 +4,43 @@
     
     /**
      * An interface to decide how links/images are handled.
    + *
    + * 

    Implementations need to be registered with a parser via {@link + * org.commonmark.parser.Parser.Builder#linkProcessor}. Then, when inline parsing is run, each + * parsed link/image is passed to the processor. This includes links like these: + * *

    - * Implementations need to be registered with a parser via {@link org.commonmark.parser.Parser.Builder#linkProcessor}. - * Then, when inline parsing is run, each parsed link/image is passed to the processor. This includes links like these: - *

    + * *

    
      * [text](destination)
      * [text]
      * [text][]
      * [text][label]
      * 
    + * * And images: + * *
    
      * ![text](destination)
      * ![text]
      * ![text][]
      * ![text][label]
      * 
    + * * See {@link LinkInfo} for accessing various parts of the parsed link/image. - *

    - * The processor can then inspect the link/image and decide what to do with it by returning the appropriate - * {@link LinkResult}. If it returns {@link LinkResult#none()}, the next registered processor is tried. If none of them - * apply, the link is handled as it normally would. + * + *

    The processor can then inspect the link/image and decide what to do with it by returning the + * appropriate {@link LinkResult}. If it returns {@link LinkResult#none()}, the next registered + * processor is tried. If none of them apply, the link is handled as it normally would. */ public interface LinkProcessor { /** * @param linkInfo information about the parsed link/image - * @param scanner the scanner at the current position after the parsed link/image - * @param context context for inline parsing - * @return what to do with the link/image, e.g. do nothing (try the next processor), wrap the text in a node, or - * replace the link/image with a node + * @param scanner the scanner at the current position after the parsed link/image + * @param context context for inline parsing + * @return what to do with the link/image, e.g. do nothing (try the next processor), wrap the + * text in a node, or replace the link/image with a node */ LinkResult process(LinkInfo linkInfo, Scanner scanner, InlineParserContext context); } diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java b/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java index 43bc82af8..6becc0cb7 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/LinkResult.java @@ -3,26 +3,24 @@ import org.commonmark.internal.inline.LinkResultImpl; import org.commonmark.node.Node; -/** - * What to do with a link/image processed by {@link LinkProcessor}. - */ +/** What to do with a link/image processed by {@link LinkProcessor}. */ public interface LinkResult { - /** - * Link not handled by processor. - */ + /** Link not handled by processor. */ static LinkResult none() { return null; } /** * Wrap the link text in a node. This is the normal behavior for links, e.g. for this: + * *

    
          * [my *text*](destination)
          * 
    - * The text is {@code my *text*}, a text node and emphasis. The text is wrapped in a - * {@link org.commonmark.node.Link} node, which means the text is added as child nodes to it. * - * @param node the node to which the link text nodes will be added as child nodes + * The text is {@code my *text*}, a text node and emphasis. The text is wrapped in a {@link + * org.commonmark.node.Link} node, which means the text is added as child nodes to it. + * + * @param node the node to which the link text nodes will be added as child nodes * @param position the position to continue parsing from */ static LinkResult wrapTextIn(Node node, Position position) { @@ -31,12 +29,15 @@ static LinkResult wrapTextIn(Node node, Position position) { /** * Replace the link with a node. E.g. for this: + * *
    
          * [^foo]
          * 
    - * The processor could decide to create a {@code FootnoteReference} node instead which replaces the link. * - * @param node the node to replace the link with + * The processor could decide to create a {@code FootnoteReference} node instead which replaces + * the link. + * + * @param node the node to replace the link with * @param position the position to continue parsing from */ static LinkResult replaceWith(Node node, Position position) { @@ -44,7 +45,8 @@ static LinkResult replaceWith(Node node, Position position) { } /** - * If a {@link LinkInfo#marker()} is present, include it in processing (i.e. treat it the same way as the brackets). + * If a {@link LinkInfo#marker()} is present, include it in processing (i.e. treat it the same + * way as the brackets). */ LinkResult includeMarker(); } diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/ParsedInline.java b/commonmark/src/main/java/org/commonmark/parser/beta/ParsedInline.java index 5d1402cae..c07a260fc 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/ParsedInline.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/ParsedInline.java @@ -1,14 +1,13 @@ package org.commonmark.parser.beta; +import java.util.Objects; import org.commonmark.internal.inline.ParsedInlineImpl; import org.commonmark.node.Node; -import java.util.Objects; - /** * The result of a single inline parser. Use the static methods to create instances. - *

    - * This interface is not intended to be implemented by clients. + * + *

    This interface is not intended to be implemented by clients. */ public interface ParsedInline { diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/Position.java b/commonmark/src/main/java/org/commonmark/parser/beta/Position.java index 3dbb4870f..5f45ef7f0 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/Position.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/Position.java @@ -1,8 +1,8 @@ package org.commonmark.parser.beta; /** - * Position within a {@link Scanner}. This is intentionally kept opaque so as not to expose the internal structure of - * the Scanner. + * Position within a {@link Scanner}. This is intentionally kept opaque so as not to expose the + * internal structure of the Scanner. */ public class Position { diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/Scanner.java b/commonmark/src/main/java/org/commonmark/parser/beta/Scanner.java index 324639493..1acf39ced 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/Scanner.java @@ -1,29 +1,29 @@ package org.commonmark.parser.beta; +import java.util.List; import org.commonmark.node.SourceSpan; import org.commonmark.parser.SourceLine; import org.commonmark.parser.SourceLines; import org.commonmark.text.CharMatcher; -import java.util.List; - public class Scanner { /** - * Character representing the end of input source (or outside of the text in case of the "previous" methods). - *

    - * Note that we can use NULL to represent this because CommonMark does not allow those in the input (we replace them - * in the beginning of parsing). + * Character representing the end of input source (or outside of the text in case of the + * "previous" methods). + * + *

    Note that we can use NULL to represent this because CommonMark does not allow those in the + * input (we replace them in the beginning of parsing). */ public static final char END = '\0'; - // Lines without newlines at the end. The scanner will yield `\n` between lines because they're significant for - // parsing and the final output. There is no `\n` after the last line. + // Lines without newlines at the end. The scanner will yield `\n` between lines because they're + // significant for parsing and the final output. There is no `\n` after the last line. private final List lines; // Which line we're at. private int lineIndex; - // The index within the line. If index == length(), we pretend that there's a `\n` and only advance after we yield - // that. + // The index within the line. If index == length(), we pretend that there's a `\n` and only + // advance after we yield that. private int index; // Current line or "" if at the end of the lines (using "" instead of null saves a null check) @@ -135,8 +135,8 @@ public boolean next(char c) { } /** - * Check if we have the specified content on the line and advanced the position. Note that if you want to match - * newline characters, use {@link #next(char)}. + * Check if we have the specified content on the line and advanced the position. Note that if + * you want to match newline characters, use {@link #next(char)}. * * @param content the text content to match on a single line (excluding newline characters) * @return true if matched and position was advanced, false otherwise @@ -221,8 +221,8 @@ public int find(CharMatcher matcher) { } } - // Don't expose the int index, because it would be good if we could switch input to a List of lines later - // instead of one contiguous String. + // Don't expose the int index, because it would be good if we could switch input to a + // List of lines later instead of one contiguous String. public Position position() { return new Position(lineIndex, index); } @@ -234,8 +234,8 @@ public void setPosition(Position position) { setLine(lines.get(this.lineIndex)); } - // For cases where the caller appends the result to a StringBuilder, we could offer another method to avoid some - // unnecessary copying. + // For cases where the caller appends the result to a StringBuilder, we could offer another + // method to avoid some unnecessary copying. public SourceLines getSource(Position begin, Position end) { if (begin.lineIndex == end.lineIndex) { // Shortcut for common case of text from a single line @@ -271,11 +271,13 @@ private void setLine(SourceLine line) { private void checkPosition(int lineIndex, int index) { if (lineIndex < 0 || lineIndex >= lines.size()) { - throw new IllegalArgumentException("Line index " + lineIndex + " out of range, number of lines: " + lines.size()); + throw new IllegalArgumentException( + "Line index " + lineIndex + " out of range, number of lines: " + lines.size()); } SourceLine line = lines.get(lineIndex); if (index < 0 || index > line.getContent().length()) { - throw new IllegalArgumentException("Index " + index + " out of range, line length: " + line.getContent().length()); + throw new IllegalArgumentException( + "Index " + index + " out of range, line length: " + line.getContent().length()); } } } diff --git a/commonmark/src/main/java/org/commonmark/parser/beta/package-info.java b/commonmark/src/main/java/org/commonmark/parser/beta/package-info.java index 029d80507..f77deb1c9 100644 --- a/commonmark/src/main/java/org/commonmark/parser/beta/package-info.java +++ b/commonmark/src/main/java/org/commonmark/parser/beta/package-info.java @@ -1,4 +1,2 @@ -/** - * Experimental APIs to use for extensions. APIs are subject to change if necessary. - */ +/** Experimental APIs to use for extensions. APIs are subject to change if necessary. */ package org.commonmark.parser.beta; diff --git a/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java b/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java index 4fb1a05ac..69610a664 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java @@ -1,13 +1,12 @@ package org.commonmark.parser.block; +import java.util.List; import org.commonmark.node.Block; import org.commonmark.node.DefinitionMap; import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; import org.commonmark.parser.SourceLine; -import java.util.List; - public abstract class AbstractBlockParser implements BlockParser { @Override @@ -26,8 +25,7 @@ public boolean canContain(Block childBlock) { } @Override - public void addLine(SourceLine line) { - } + public void addLine(SourceLine line) {} @Override public void addSourceSpan(SourceSpan sourceSpan) { @@ -40,11 +38,8 @@ public List> getDefinitions() { } @Override - public void closeBlock() { - } + public void closeBlock() {} @Override - public void parseInlines(InlineParser inlineParser) { - } - + public void parseInlines(InlineParser inlineParser) {} } diff --git a/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParserFactory.java b/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParserFactory.java index e297ac831..1f19d59b8 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParserFactory.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParserFactory.java @@ -1,4 +1,3 @@ package org.commonmark.parser.block; -public abstract class AbstractBlockParserFactory implements BlockParserFactory { -} +public abstract class AbstractBlockParserFactory implements BlockParserFactory {} diff --git a/commonmark/src/main/java/org/commonmark/parser/block/BlockContinue.java b/commonmark/src/main/java/org/commonmark/parser/block/BlockContinue.java index dc2616065..d57656102 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockContinue.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockContinue.java @@ -2,13 +2,10 @@ import org.commonmark.internal.BlockContinueImpl; -/** - * Result object for continuing parsing of a block, see static methods for constructors. - */ +/** Result object for continuing parsing of a block, see static methods for constructors. */ public class BlockContinue { - protected BlockContinue() { - } + protected BlockContinue() {} public static BlockContinue none() { return null; @@ -25,5 +22,4 @@ public static BlockContinue atColumn(int newColumn) { public static BlockContinue finished() { return new BlockContinueImpl(-1, -1, true); } - } diff --git a/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java b/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java index 32ff2a474..e18105529 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java @@ -1,33 +1,34 @@ package org.commonmark.parser.block; +import java.util.List; import org.commonmark.node.Block; import org.commonmark.node.DefinitionMap; import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; import org.commonmark.parser.SourceLine; -import java.util.List; - /** * Parser for a specific block node. - *

    - * Implementations should subclass {@link AbstractBlockParser} instead of implementing this directly. + * + *

    Implementations should subclass {@link AbstractBlockParser} instead of implementing this + * directly. */ public interface BlockParser { /** - * Return true if the block that is parsed is a container (contains other blocks), or false if it's a leaf. + * Return true if the block that is parsed is a container (contains other blocks), or false if + * it's a leaf. */ boolean isContainer(); /** * Return true if the block can have lazy continuation lines. - *

    - * Lazy continuation lines are lines that were rejected by this {@link #tryContinue(ParserState)} but didn't match - * any other block parsers either. - *

    - * If true is returned here, those lines will get added via {@link #addLine(SourceLine)}. For false, the block is - * closed instead. + * + *

    Lazy continuation lines are lines that were rejected by this {@link + * #tryContinue(ParserState)} but didn't match any other block parsers either. + * + *

    If true is returned here, those lines will get added via {@link #addLine(SourceLine)}. For + * false, the block is closed instead. */ boolean canHaveLazyContinuationLines(); @@ -38,28 +39,28 @@ public interface BlockParser { BlockContinue tryContinue(ParserState parserState); /** - * Add the part of a line that belongs to this block parser to parse (i.e. without any container block markers). - * Note that the line will only include a {@link SourceLine#getSourceSpan()} if source spans are enabled for inlines. + * Add the part of a line that belongs to this block parser to parse (i.e. without any container + * block markers). Note that the line will only include a {@link SourceLine#getSourceSpan()} if + * source spans are enabled for inlines. */ void addLine(SourceLine line); /** - * Add a source span of the currently parsed block. The default implementation in {@link AbstractBlockParser} adds - * it to the block. Unless you have some complicated parsing where you need to check source positions, you don't - * need to override this. + * Add a source span of the currently parsed block. The default implementation in {@link + * AbstractBlockParser} adds it to the block. Unless you have some complicated parsing where you + * need to check source positions, you don't need to override this. * * @since 0.16.0 */ void addSourceSpan(SourceSpan sourceSpan); /** - * Return definitions parsed by this parser. The definitions returned here can later be accessed during inline - * parsing via {@link org.commonmark.parser.InlineParserContext#getDefinition}. + * Return definitions parsed by this parser. The definitions returned here can later be accessed + * during inline parsing via {@link org.commonmark.parser.InlineParserContext#getDefinition}. */ List> getDefinitions(); void closeBlock(); void parseInlines(InlineParser inlineParser); - } diff --git a/commonmark/src/main/java/org/commonmark/parser/block/BlockParserFactory.java b/commonmark/src/main/java/org/commonmark/parser/block/BlockParserFactory.java index 729224761..ced38633b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockParserFactory.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockParserFactory.java @@ -2,11 +2,11 @@ /** * Parser factory for a block node for determining when a block starts. - *

    - * Implementations should subclass {@link AbstractBlockParserFactory} instead of implementing this directly. + * + *

    Implementations should subclass {@link AbstractBlockParserFactory} instead of implementing + * this directly. */ public interface BlockParserFactory { BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser); - } diff --git a/commonmark/src/main/java/org/commonmark/parser/block/BlockStart.java b/commonmark/src/main/java/org/commonmark/parser/block/BlockStart.java index c41f1caa3..df26395bb 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockStart.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockStart.java @@ -2,24 +2,17 @@ import org.commonmark.internal.BlockStartImpl; -/** - * Result object for starting parsing of a block, see static methods for constructors. - */ +/** Result object for starting parsing of a block, see static methods for constructors. */ public abstract class BlockStart { - protected BlockStart() { - } + protected BlockStart() {} - /** - * Result for when there is no block start. - */ + /** Result for when there is no block start. */ public static BlockStart none() { return null; } - /** - * Start block(s) with the specified parser(s). - */ + /** Start block(s) with the specified parser(s). */ public static BlockStart of(BlockParser... blockParsers) { return new BlockStartImpl(blockParsers); } @@ -39,30 +32,32 @@ public static BlockStart of(BlockParser... blockParsers) { public abstract BlockStart atColumn(int newColumn); /** - * @deprecated use {@link #replaceParagraphLines(int)} instead; please raise an issue if that doesn't work for you - * for some reason. + * @deprecated use {@link #replaceParagraphLines(int)} instead; please raise an issue if that + * doesn't work for you for some reason. */ @Deprecated public abstract BlockStart replaceActiveBlockParser(); /** - * Replace a number of lines from the current paragraph (as returned by - * {@link MatchedBlockParser#getParagraphLines()}) with the new block. - *

    - * This is useful for parsing blocks that start with normal paragraphs and only have special marker syntax in later - * lines, e.g. in this: + * Replace a number of lines from the current paragraph (as returned by {@link + * MatchedBlockParser#getParagraphLines()}) with the new block. + * + *

    This is useful for parsing blocks that start with normal paragraphs and only have special + * marker syntax in later lines, e.g. in this: + * *

          * Foo
          * ===
          * 
    - * The Foo line is initially parsed as a normal paragraph, then === is parsed as a heading - * marker, replacing the 1 paragraph line before. The end result is a single Heading block. - *

    - * Note that source spans from the replaced lines are automatically added to the new block. * - * @param lines the number of lines to replace (at least 1); use {@link Integer#MAX_VALUE} to replace the whole - * paragraph + * The Foo line is initially parsed as a normal paragraph, then === is + * parsed as a heading marker, replacing the 1 paragraph line before. The end result is a single + * Heading block. + * + *

    Note that source spans from the replaced lines are automatically added to the new block. + * + * @param lines the number of lines to replace (at least 1); use {@link Integer#MAX_VALUE} to + * replace the whole paragraph */ public abstract BlockStart replaceParagraphLines(int lines); - } diff --git a/commonmark/src/main/java/org/commonmark/parser/block/MatchedBlockParser.java b/commonmark/src/main/java/org/commonmark/parser/block/MatchedBlockParser.java index c4619d8c2..3ad49f0ec 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/MatchedBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/MatchedBlockParser.java @@ -3,20 +3,21 @@ import org.commonmark.parser.SourceLines; /** - * Open block parser that was last matched during the continue phase. This is different from the currently active - * block parser, as an unmatched block is only closed when a new block is started. - *

    This interface is not intended to be implemented by clients.

    + * Open block parser that was last matched during the continue phase. This is different from the + * currently active block parser, as an unmatched block is only closed when a new block is started. + * + *

    This interface is not intended to be implemented by clients. */ public interface MatchedBlockParser { BlockParser getMatchedBlockParser(); /** - * Returns the current paragraph lines if the matched block is a paragraph. If you want to use some or all of the - * lines for starting a new block instead, use {@link BlockStart#replaceParagraphLines(int)}. + * Returns the current paragraph lines if the matched block is a paragraph. If you want to use + * some or all of the lines for starting a new block instead, use {@link + * BlockStart#replaceParagraphLines(int)}. * * @return paragraph content or an empty list */ SourceLines getParagraphLines(); - } diff --git a/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java b/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java index b32bbaee5..e9e3ec1b3 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java @@ -4,7 +4,8 @@ /** * State of the parser that is used in block parsers. - *

    This interface is not intended to be implemented by clients.

    + * + *

    This interface is not intended to be implemented by clients. */ public interface ParserState { @@ -19,21 +20,24 @@ public interface ParserState { int getIndex(); /** - * @return the index of the next non-space character starting from {@link #getIndex()} (may be the same) (0-based) + * @return the index of the next non-space character starting from {@link #getIndex()} (may be + * the same) (0-based) */ int getNextNonSpaceIndex(); /** - * The column is the position within the line after tab characters have been processed as 4-space tab stops. - * If the line doesn't contain any tabs, it's the same as the {@link #getIndex()}. If the line starts with a tab, - * followed by text, then the column for the first character of the text is 4 (the index is 1). + * The column is the position within the line after tab characters have been processed as + * 4-space tab stops. If the line doesn't contain any tabs, it's the same as the {@link + * #getIndex()}. If the line starts with a tab, followed by text, then the column for the first + * character of the text is 4 (the index is 1). * * @return the current column within the line (0-based) */ int getColumn(); /** - * @return the indentation in columns (either by spaces or tab stop of 4), starting from {@link #getColumn()} + * @return the indentation in columns (either by spaces or tab stop of 4), starting from {@link + * #getColumn()} */ int getIndent(); @@ -46,5 +50,4 @@ public interface ParserState { * @return the deepest open block parser */ BlockParser getActiveBlockParser(); - } diff --git a/commonmark/src/main/java/org/commonmark/parser/block/package-info.java b/commonmark/src/main/java/org/commonmark/parser/block/package-info.java index 095d4d565..dbb4adb1d 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/package-info.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/package-info.java @@ -1,4 +1,2 @@ -/** - * Types for extending block parsing - */ +/** Types for extending block parsing */ package org.commonmark.parser.block; diff --git a/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java index 3b6abf214..67467210e 100644 --- a/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java @@ -4,22 +4,24 @@ /** * Custom delimiter processor for additional delimiters besides {@code _} and {@code *}. - *

    - * Note that implementations of this need to be thread-safe, the same instance may be used by multiple parsers. + * + *

    Note that implementations of this need to be thread-safe, the same instance may be used by + * multiple parsers. * * @see org.commonmark.parser.beta.InlineContentParserFactory */ public interface DelimiterProcessor { /** - * @return the character that marks the beginning of a delimited node, must not clash with any built-in special - * characters + * @return the character that marks the beginning of a delimited node, must not clash with any + * built-in special characters */ char getOpeningCharacter(); /** - * @return the character that marks the the ending of a delimited node, must not clash with any built-in special - * characters. Note that for a symmetric delimiter such as "*", this is the same as the opening. + * @return the character that marks the the ending of a delimited node, must not clash with any + * built-in special characters. Note that for a symmetric delimiter such as "*", this is the + * same as the opening. */ char getClosingCharacter(); @@ -30,17 +32,18 @@ public interface DelimiterProcessor { /** * Process the delimiter runs. - *

    - * The processor can examine the runs and the nodes and decide if it wants to process or not. If not, it should not - * change any nodes and return 0. If yes, it should do the processing (wrapping nodes, etc) and then return how many - * delimiters were used. - *

    - * Note that removal (unlinking) of the used delimiter {@link Text} nodes is done by the caller. + * + *

    The processor can examine the runs and the nodes and decide if it wants to process or not. + * If not, it should not change any nodes and return 0. If yes, it should do the processing + * (wrapping nodes, etc) and then return how many delimiters were used. + * + *

    Note that removal (unlinking) of the used delimiter {@link Text} nodes is done by the + * caller. * * @param openingRun the opening delimiter run * @param closingRun the closing delimiter run - * @return how many delimiters were used; must not be greater than length of either opener or closer + * @return how many delimiters were used; must not be greater than length of either opener or + * closer */ int process(DelimiterRun openingRun, DelimiterRun closingRun); - } diff --git a/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java index 578eac96b..fd449c6b2 100644 --- a/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java +++ b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java @@ -2,9 +2,7 @@ import org.commonmark.node.Text; -/** - * A delimiter run is one or more of the same delimiter character, e.g. {@code ***}. - */ +/** A delimiter run is one or more of the same delimiter character, e.g. {@code ***}. */ public interface DelimiterRun { /** @@ -23,8 +21,8 @@ public interface DelimiterRun { int length(); /** - * @return the number of characters originally in this delimiter run; at the start of processing, this is the same - * as {{@link #length()}} + * @return the number of characters originally in this delimiter run; at the start of + * processing, this is the same as {{@link #length()}} */ int originalLength(); @@ -39,20 +37,20 @@ public interface DelimiterRun { Text getCloser(); /** - * Get the opening delimiter nodes for the specified length of delimiters. Length must be between 1 and - * {@link #length()}. - *

    - * For example, for a delimiter run {@code ***}, calling this with 1 would return the last {@code *}. - * Calling it with 2 would return the second last {@code *} and the last {@code *}. + * Get the opening delimiter nodes for the specified length of delimiters. Length must be + * between 1 and {@link #length()}. + * + *

    For example, for a delimiter run {@code ***}, calling this with 1 would return the last + * {@code *}. Calling it with 2 would return the second last {@code *} and the last {@code *}. */ Iterable getOpeners(int length); /** - * Get the closing delimiter nodes for the specified length of delimiters. Length must be between 1 and - * {@link #length()}. - *

    - * For example, for a delimiter run {@code ***}, calling this with 1 would return the first {@code *}. - * Calling it with 2 would return the first {@code *} and the second {@code *}. + * Get the closing delimiter nodes for the specified length of delimiters. Length must be + * between 1 and {@link #length()}. + * + *

    For example, for a delimiter run {@code ***}, calling this with 1 would return the first + * {@code *}. Calling it with 2 would return the first {@code *} and the second {@code *}. */ Iterable getClosers(int length); } diff --git a/commonmark/src/main/java/org/commonmark/parser/package-info.java b/commonmark/src/main/java/org/commonmark/parser/package-info.java index 2afb3b96d..753d665dd 100644 --- a/commonmark/src/main/java/org/commonmark/parser/package-info.java +++ b/commonmark/src/main/java/org/commonmark/parser/package-info.java @@ -1,4 +1,2 @@ -/** - * Parsing input text to AST nodes (see {@link org.commonmark.parser.Parser}) - */ +/** Parsing input text to AST nodes (see {@link org.commonmark.parser.Parser}) */ package org.commonmark.parser; diff --git a/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java index 4ae4b5dcd..e8838eb5f 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java @@ -1,12 +1,9 @@ package org.commonmark.renderer; -import org.commonmark.node.Node; - import java.util.Set; +import org.commonmark.node.Node; -/** - * A renderer for a set of node types. - */ +/** A renderer for a set of node types. */ public interface NodeRenderer { /** @@ -26,14 +23,12 @@ public interface NodeRenderer { * * @param rootNode the root (top-level) node */ - default void beforeRoot(Node rootNode) { - } + default void beforeRoot(Node rootNode) {} /** * Called after the root node is rendered, to do any final processing at the end. * * @param rootNode the root (top-level) node */ - default void afterRoot(Node rootNode) { - } + default void afterRoot(Node rootNode) {} } diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProvider.java b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProvider.java index 24a471d46..157eaf8a3 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProvider.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProvider.java @@ -1,29 +1,27 @@ package org.commonmark.renderer.html; -import org.commonmark.node.Node; - import java.util.Map; +import org.commonmark.node.Node; -/** - * Extension point for adding/changing attributes on HTML tags for a node. - */ +/** Extension point for adding/changing attributes on HTML tags for a node. */ public interface AttributeProvider { /** * Set the attributes for a HTML tag of the specified node by modifying the provided map. - *

    - * This allows to change or even remove default attributes. With great power comes great responsibility. - *

    - * The attribute key and values will be escaped (preserving character entities), so don't escape them here, - * otherwise they will be double-escaped. - *

    - * This method may be called multiple times for the same node, if the node is rendered using multiple nested - * tags (e.g. code blocks). + * + *

    This allows to change or even remove default attributes. With great power comes great + * responsibility. + * + *

    The attribute key and values will be escaped (preserving character entities), so don't + * escape them here, otherwise they will be double-escaped. + * + *

    This method may be called multiple times for the same node, if the node is rendered using + * multiple nested tags (e.g. code blocks). * * @param node the node to set attributes for - * @param tagName the HTML tag name that these attributes are for (e.g. {@code h1}, {@code pre}, {@code code}). + * @param tagName the HTML tag name that these attributes are for (e.g. {@code h1}, {@code pre}, + * {@code code}). * @param attributes the attributes, with any default attributes already set in the map */ void setAttributes(Node node, String tagName, Map attributes); - } diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderContext.java b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderContext.java index 0959932bc..cddaec43a 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderContext.java @@ -2,8 +2,9 @@ /** * The context for attribute providers. - *

    Note: There are currently no methods here, this is for future extensibility.

    - *

    This interface is not intended to be implemented by clients.

    + * + *

    Note: There are currently no methods here, this is for future extensibility. + * + *

    This interface is not intended to be implemented by clients. */ -public interface AttributeProviderContext { -} +public interface AttributeProviderContext {} diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderFactory.java b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderFactory.java index d4c12ca06..d093cbf98 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderFactory.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderFactory.java @@ -1,8 +1,6 @@ package org.commonmark.renderer.html; -/** - * Factory for instantiating new attribute providers when rendering is done. - */ +/** Factory for instantiating new attribute providers when rendering is done. */ public interface AttributeProviderFactory { /** diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java index 5c536558e..d5e0d8afc 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -1,11 +1,10 @@ package org.commonmark.renderer.html; -import org.commonmark.node.*; -import org.commonmark.renderer.NodeRenderer; - import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import org.commonmark.node.*; +import org.commonmark.renderer.NodeRenderer; /** * The node renderer that renders all the core nodes (comes last in the order of node renderers). @@ -42,8 +41,7 @@ public Set> getNodeTypes() { Code.class, HtmlInline.class, SoftLineBreak.class, - HardLineBreak.class - ); + HardLineBreak.class); } @Override @@ -69,9 +67,12 @@ public void visit(Heading heading) { @Override public void visit(Paragraph paragraph) { - boolean omitP = isInTightList(paragraph) || // - (context.shouldOmitSingleParagraphP() && paragraph.getParent() instanceof Document && // - paragraph.getPrevious() == null && paragraph.getNext() == null); + boolean omitP = + isInTightList(paragraph) + || (context.shouldOmitSingleParagraphP() + && paragraph.getParent() instanceof Document + && paragraph.getPrevious() == null + && paragraph.getNext() == null); if (!omitP) { html.line(); html.tag("p", getAttrs(paragraph, "p")); @@ -172,7 +173,8 @@ public void visit(ListItem listItem) { @Override public void visit(OrderedList orderedList) { - int start = orderedList.getMarkerStartNumber() != null ? orderedList.getMarkerStartNumber() : 1; + int start = + orderedList.getMarkerStartNumber() != null ? orderedList.getMarkerStartNumber() : 1; Map attrs = new LinkedHashMap<>(); if (start != 1) { attrs.put("start", String.valueOf(start)); @@ -268,7 +270,8 @@ private void renderCodeBlock(String literal, Node node, Map attr html.line(); } - private void renderListBlock(ListBlock listBlock, String tagName, Map attributes) { + private void renderListBlock( + ListBlock listBlock, String tagName, Map attributes) { html.line(); html.tag(tagName, attributes); html.line(); @@ -294,7 +297,8 @@ private Map getAttrs(Node node, String tagName) { return getAttrs(node, tagName, Map.of()); } - private Map getAttrs(Node node, String tagName, Map defaultAttributes) { + private Map getAttrs( + Node node, String tagName, Map defaultAttributes) { return context.extendAttributes(node, tagName, defaultAttributes); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java b/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java index 4c5bed12c..bdf59e809 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java @@ -3,10 +3,9 @@ import java.util.*; /** - * - * Allows http, https, mailto, and data protocols for url. - * Also allows protocol relative urls, and relative urls. - * Implementation based on https://github.com/OWASP/java-html-sanitizer/blob/f07e44b034a45d94d6fd010279073c38b6933072/src/main/java/org/owasp/html/FilterUrlByProtocolAttributePolicy.java + * Allows http, https, mailto, and data protocols for url. Also allows protocol relative urls, and + * relative urls. Implementation based on + * https://github.com/OWASP/java-html-sanitizer/blob/f07e44b034a45d94d6fd010279073c38b6933072/src/main/java/org/owasp/html/FilterUrlByProtocolAttributePolicy.java */ public class DefaultUrlSanitizer implements UrlSanitizer { private Set protocols; @@ -27,7 +26,7 @@ public String sanitizeLinkUrl(String url) { switch (url.charAt(i)) { case '/': case '#': - case '?': // No protocol. + case '?': // No protocol. break protocol_loop; case ':': String protocol = url.substring(0, i).toLowerCase(); @@ -40,7 +39,6 @@ public String sanitizeLinkUrl(String url) { return url; } - @Override public String sanitizeImageUrl(String url) { return sanitizeLinkUrl(url); @@ -74,7 +72,6 @@ private boolean isHtmlSpace(int ch) { return true; default: return false; - } } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java index eecff0f44..b9119f0e5 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java @@ -1,11 +1,10 @@ package org.commonmark.renderer.html; +import java.util.Map; import org.commonmark.node.Image; import org.commonmark.node.Link; import org.commonmark.node.Node; -import java.util.Map; - public interface HtmlNodeRendererContext { /** @@ -17,8 +16,9 @@ public interface HtmlNodeRendererContext { /** * Let extensions modify the HTML tag attributes. * - * @param node the node for which the attributes are applied - * @param tagName the HTML tag name that these attributes are for (e.g. {@code h1}, {@code pre}, {@code code}). + * @param node the node for which the attributes are applied + * @param tagName the HTML tag name that these attributes are for (e.g. {@code h1}, {@code pre}, + * {@code code}). * @param attributes the attributes that were calculated by the renderer * @return the extended attributes with added/updated/removed entries */ @@ -35,8 +35,9 @@ public interface HtmlNodeRendererContext { String getSoftbreak(); /** - * Render the specified node and its children using the configured renderers. This should be used to render child - * nodes; be careful not to pass the node that is being rendered, that would result in an endless loop. + * Render the specified node and its children using the configured renderers. This should be + * used to render child nodes; be careful not to pass the node that is being rendered, that + * would result in an endless loop. * * @param node the node to render */ @@ -48,7 +49,8 @@ public interface HtmlNodeRendererContext { boolean shouldEscapeHtml(); /** - * @return whether documents that only contain a single paragraph should be rendered without the {@code

    } tag + * @return whether documents that only contain a single paragraph should be rendered without the + * {@code

    } tag */ boolean shouldOmitSingleParagraphP(); @@ -59,7 +61,8 @@ public interface HtmlNodeRendererContext { boolean shouldSanitizeUrls(); /** - * @return Sanitizer to use for securing {@link Link} href and {@link Image} src if {@link #shouldSanitizeUrls()} is true. + * @return Sanitizer to use for securing {@link Link} href and {@link Image} src if {@link + * #shouldSanitizeUrls()} is true. * @since 0.14.0 */ UrlSanitizer urlSanitizer(); diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererFactory.java index 8a343bf0f..cbfd9bc65 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererFactory.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererFactory.java @@ -2,9 +2,7 @@ import org.commonmark.renderer.NodeRenderer; -/** - * Factory for instantiating new node renderers when rendering is done. - */ +/** Factory for instantiating new node renderers when rendering is done. */ public interface HtmlNodeRendererFactory { /** diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java index b0264fc72..46ae217ff 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java @@ -1,17 +1,17 @@ package org.commonmark.renderer.html; +import java.util.*; import org.commonmark.Extension; import org.commonmark.internal.renderer.NodeRendererMap; import org.commonmark.internal.util.Escaping; import org.commonmark.node.*; import org.commonmark.renderer.Renderer; -import java.util.*; - /** * Renders a tree of nodes to HTML. - *

    - * Start with the {@link #builder} method to configure the renderer. Example: + * + *

    Start with the {@link #builder} method to configure the renderer. Example: + * *

    
      * HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build();
      * renderer.render(node);
    @@ -69,9 +69,7 @@ public String render(Node node) {
             return sb.toString();
         }
     
    -    /**
    -     * Builder for configuring an {@link HtmlRenderer}. See methods for default configuration.
    -     */
    +    /** Builder for configuring an {@link HtmlRenderer}. See methods for default configuration. */
         public static class Builder {
     
             private String softbreak = "\n";
    @@ -91,12 +89,12 @@ public HtmlRenderer build() {
             }
     
             /**
    -         * The HTML to use for rendering a softbreak, defaults to {@code "\n"} (meaning the rendered result doesn't have
    -         * a line break).
    -         * 

    - * Set it to {@code "
    "} (or {@code "
    "} to make them hard breaks. - *

    - * Set it to {@code " "} to ignore line wrapping in the source. + * The HTML to use for rendering a softbreak, defaults to {@code "\n"} (meaning the rendered + * result doesn't have a line break). + * + *

    Set it to {@code "
    "} (or {@code "
    "} to make them hard breaks. + * + *

    Set it to {@code " "} to ignore line wrapping in the source. * * @param softbreak HTML for softbreak * @return {@code this} @@ -107,10 +105,12 @@ public Builder softbreak(String softbreak) { } /** - * Whether {@link HtmlInline} and {@link HtmlBlock} should be escaped, defaults to {@code false}. - *

    - * Note that {@link HtmlInline} is only a tag itself, not the text between an opening tag and a closing tag. So - * markup in the text will be parsed as normal and is not affected by this option. + * Whether {@link HtmlInline} and {@link HtmlBlock} should be escaped, defaults to {@code + * false}. + * + *

    Note that {@link HtmlInline} is only a tag itself, not the text between an opening tag + * and a closing tag. So markup in the text will be parsed as normal and is not affected by + * this option. * * @param escapeHtml true for escaping, false for preserving raw HTML * @return {@code this} @@ -121,7 +121,8 @@ public Builder escapeHtml(boolean escapeHtml) { } /** - * Whether {@link Image} src and {@link Link} href should be sanitized, defaults to {@code false}. + * Whether {@link Image} src and {@link Link} href should be sanitized, defaults to {@code + * false}. * * @param sanitizeUrls true for sanitization, false for preserving raw attribute * @return {@code this} @@ -146,13 +147,15 @@ public Builder urlSanitizer(UrlSanitizer urlSanitizer) { /** * Whether URLs of link or images should be percent-encoded, defaults to {@code false}. - *

    - * If enabled, the following is done: + * + *

    If enabled, the following is done: + * *

      - *
    • Existing percent-encoded parts are preserved (e.g. "%20" is kept as "%20")
    • - *
    • Reserved characters such as "/" are preserved, except for "[" and "]" (see encodeURI in JS)
    • - *
    • Unreserved characters such as "a" are preserved
    • - *
    • Other characters such umlauts are percent-encoded
    • + *
    • Existing percent-encoded parts are preserved (e.g. "%20" is kept as "%20") + *
    • Reserved characters such as "/" are preserved, except for "[" and "]" (see + * encodeURI in JS) + *
    • Unreserved characters such as "a" are preserved + *
    • Other characters such umlauts are percent-encoded *
    * * @param percentEncodeUrls true to percent-encode, false for leaving as-is @@ -164,8 +167,9 @@ public Builder percentEncodeUrls(boolean percentEncodeUrls) { } /** - * Whether documents that only contain a single paragraph should be rendered without the {@code

    } tag. Set to - * {@code true} to render without the tag; the default of {@code false} always renders the tag. + * Whether documents that only contain a single paragraph should be rendered without the + * {@code

    } tag. Set to {@code true} to render without the tag; the default of {@code + * false} always renders the tag. * * @return {@code this} */ @@ -175,23 +179,26 @@ public Builder omitSingleParagraphP(boolean omitSingleParagraphP) { } /** - * Add a factory for an attribute provider for adding/changing HTML attributes to the rendered tags. + * Add a factory for an attribute provider for adding/changing HTML attributes to the + * rendered tags. * * @param attributeProviderFactory the attribute provider factory to add * @return {@code this} */ public Builder attributeProviderFactory(AttributeProviderFactory attributeProviderFactory) { - Objects.requireNonNull(attributeProviderFactory, "attributeProviderFactory must not be null"); + Objects.requireNonNull( + attributeProviderFactory, "attributeProviderFactory must not be null"); this.attributeProviderFactories.add(attributeProviderFactory); return this; } /** - * Add a factory for instantiating a node renderer (done when rendering). This allows to override the rendering - * of node types or define rendering for custom node types. - *

    - * If multiple node renderers for the same node type are created, the one from the factory that was added first - * "wins". (This is how the rendering for core node types can be overridden; the default rendering comes last.) + * Add a factory for instantiating a node renderer (done when rendering). This allows to + * override the rendering of node types or define rendering for custom node types. + * + *

    If multiple node renderers for the same node type are created, the one from the + * factory that was added first "wins". (This is how the rendering for core node types can + * be overridden; the default rendering comes last.) * * @param nodeRendererFactory the factory for creating a node renderer * @return {@code this} @@ -218,9 +225,7 @@ public Builder extensions(Iterable extensions) { } } - /** - * Extension for {@link HtmlRenderer}. - */ + /** Extension for {@link HtmlRenderer}. */ public interface HtmlRendererExtension extends Extension { void extend(Builder rendererBuilder); } @@ -275,7 +280,8 @@ public String encodeUrl(String url) { } @Override - public Map extendAttributes(Node node, String tagName, Map attributes) { + public Map extendAttributes( + Node node, String tagName, Map attributes) { Map attrs = new LinkedHashMap<>(attributes); setCustomAttributes(node, tagName, attrs); return attrs; diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java index a4ac05d45..ebe638dc3 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java @@ -1,10 +1,9 @@ package org.commonmark.renderer.html; -import org.commonmark.internal.util.Escaping; - import java.io.IOException; import java.util.Map; import java.util.Objects; +import org.commonmark.internal.util.Escaping; public class HtmlWriter { diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/UrlSanitizer.java b/commonmark/src/main/java/org/commonmark/renderer/html/UrlSanitizer.java index fb48ca361..dea6d55d2 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/UrlSanitizer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/UrlSanitizer.java @@ -4,10 +4,11 @@ import org.commonmark.node.Link; /** - * Sanitizes urls for img and a elements by whitelisting protocols. - * This is intended to prevent XSS payloads like [Click this totally safe url](javascript:document.xss=true;) - *

    - * Implementation based on https://github.com/OWASP/java-html-sanitizer/blob/f07e44b034a45d94d6fd010279073c38b6933072/src/main/java/org/owasp/html/FilterUrlByProtocolAttributePolicy.java + * Sanitizes urls for img and a elements by whitelisting protocols. This is intended to prevent XSS + * payloads like [Click this totally safe url](javascript:document.xss=true;) + * + *

    Implementation based on + * https://github.com/OWASP/java-html-sanitizer/blob/f07e44b034a45d94d6fd010279073c38b6933072/src/main/java/org/owasp/html/FilterUrlByProtocolAttributePolicy.java * * @since 0.14.0 */ diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/package-info.java b/commonmark/src/main/java/org/commonmark/renderer/html/package-info.java index 014a4c69c..090ddc7e2 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/package-info.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/package-info.java @@ -1,4 +1,2 @@ -/** - * HTML rendering (see {@link org.commonmark.renderer.html.HtmlRenderer}) - */ +/** HTML rendering (see {@link org.commonmark.renderer.html.HtmlRenderer}) */ package org.commonmark.renderer.html; diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java index 5a81676f4..d4025128b 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -1,22 +1,22 @@ package org.commonmark.renderer.markdown; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; import org.commonmark.text.AsciiMatcher; import org.commonmark.text.CharMatcher; import org.commonmark.text.Characters; -import java.util.List; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - /** * The node renderer that renders all the core nodes (comes last in the order of node renderers). - *

    - * Note that while sometimes it would be easier to record what kind of syntax was used on parsing (e.g. ATX vs Setext - * heading), this renderer is intended to also work for documents that were created by directly creating - * {@link Node Nodes} instead. So in order to support that, it sometimes needs to do a bit more work. + * + *

    Note that while sometimes it would be easier to record what kind of syntax was used on parsing + * (e.g. ATX vs Setext heading), this renderer is intended to also work for documents that were + * created by directly creating {@link Node Nodes} instead. So in order to support that, it + * sometimes needs to do a bit more work. */ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer { @@ -33,9 +33,10 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen protected final MarkdownNodeRendererContext context; private final MarkdownWriter writer; + /** - * If we're currently within a {@link BulletList} or {@link OrderedList}, this keeps the context of that list. - * It has a parent field so that it can represent a stack (for nested lists). + * If we're currently within a {@link BulletList} or {@link OrderedList}, this keeps the context + * of that list. It has a parent field so that it can represent a stack (for nested lists). */ private ListHolder listHolder; @@ -43,7 +44,11 @@ public CoreMarkdownNodeRenderer(MarkdownNodeRendererContext context) { this.context = context; this.writer = context.getWriter(); - textEscape = AsciiMatcher.builder().anyOf("[]<>`*_&\n\\").anyOf(context.getSpecialCharacters()).build(); + textEscape = + AsciiMatcher.builder() + .anyOf("[]<>`*_&\n\\") + .anyOf(context.getSpecialCharacters()) + .build(); textEscapeInHeading = AsciiMatcher.builder(textEscape).anyOf("#").build(); } @@ -69,8 +74,7 @@ public Set> getNodeTypes() { SoftLineBreak.class, StrongEmphasis.class, Text.class, - ThematicBreak.class - ); + ThematicBreak.class); } @Override @@ -108,8 +112,8 @@ public void visit(Heading heading) { visitChildren(heading); writer.line(); if (heading.getLevel() == 1) { - // Note that it would be nice to match the length of the contents instead of just using 3, but that's - // not easy. + // Note that it would be nice to match the length of the contents instead of + // just using 3, but that's not easy. writer.raw("==="); } else { writer.raw("---"); @@ -132,8 +136,8 @@ public void visit(Heading heading) { @Override public void visit(IndentedCodeBlock indentedCodeBlock) { String literal = indentedCodeBlock.getLiteral(); - // We need to respect line prefixes which is why we need to write it line by line (e.g. an indented code block - // within a block quote) + // We need to respect line prefixes which is why we need to write it line by line (e.g. an + // indented code block within a block quote) writer.writePrefix(" "); writer.pushPrefix(" "); List lines = getLines(literal); @@ -151,19 +155,24 @@ public void visit(IndentedCodeBlock indentedCodeBlock) { @Override public void visit(FencedCodeBlock codeBlock) { String literal = codeBlock.getLiteral(); - String fenceChar = codeBlock.getFenceCharacter() != null ? codeBlock.getFenceCharacter() : "`"; + String fenceChar = + codeBlock.getFenceCharacter() != null ? codeBlock.getFenceCharacter() : "`"; int openingFenceLength; if (codeBlock.getOpeningFenceLength() != null) { // If we have a known fence length, use it openingFenceLength = codeBlock.getOpeningFenceLength(); } else { - // Otherwise, calculate the closing fence length pessimistically, e.g. if the code block itself contains a - // line with ```, we need to use a fence of length 4. If ``` occurs with non-whitespace characters on a + // Otherwise, calculate the closing fence length pessimistically, e.g. if the code block + // itself contains a line with ```, we need to use a fence of length 4. If ``` occurs + // with non-whitespace characters on a // line, we technically don't need a longer fence, but it's not incorrect to do so. int fenceCharsInLiteral = findMaxRunLength(fenceChar, literal); openingFenceLength = Math.max(fenceCharsInLiteral + 1, 3); } - int closingFenceLength = codeBlock.getClosingFenceLength() != null ? codeBlock.getClosingFenceLength() : openingFenceLength; + int closingFenceLength = + codeBlock.getClosingFenceLength() != null + ? codeBlock.getClosingFenceLength() + : openingFenceLength; String openingFence = repeat(fenceChar, openingFenceLength); String closingFence = repeat(fenceChar, closingFenceLength); @@ -251,13 +260,19 @@ public void visit(ListItem listItem) { marker = repeat(" ", markerIndent) + bulletListHolder.marker; } else if (listHolder instanceof OrderedListHolder) { OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; - marker = repeat(" ", markerIndent) + orderedListHolder.number + orderedListHolder.delimiter; + marker = + repeat(" ", markerIndent) + + orderedListHolder.number + + orderedListHolder.delimiter; orderedListHolder.number++; } else { throw new IllegalStateException("Unknown list holder type: " + listHolder); } Integer contentIndent = listItem.getContentIndent(); - String spaces = contentIndent != null ? repeat(" ", Math.max(contentIndent - marker.length(), 1)) : " "; + String spaces = + contentIndent != null + ? repeat(" ", Math.max(contentIndent - marker.length(), 1)) + : " "; writer.writePrefix(marker); writer.writePrefix(spaces); writer.pushPrefix(repeat(" ", marker.length() + spaces.length())); @@ -281,10 +296,14 @@ public void visit(Code code) { writer.raw('`'); } // If the literal starts or ends with a backtick, surround it with a single space. - // If it starts and ends with a space (but is not only spaces), add an additional space (otherwise they would - // get removed on parsing). - boolean addSpace = literal.startsWith("`") || literal.endsWith("`") || - (literal.startsWith(" ") && literal.endsWith(" ") && Characters.hasNonSpace(literal)); + // If it starts and ends with a space (but is not only spaces), add an additional space + // (otherwise they would get removed on parsing). + boolean addSpace = + literal.startsWith("`") + || literal.endsWith("`") + || (literal.startsWith(" ") + && literal.endsWith(" ") + && Characters.hasNonSpace(literal)); if (addSpace) { writer.raw(' '); } @@ -345,38 +364,47 @@ public void visit(SoftLineBreak softLineBreak) { @Override public void visit(Text text) { - // Text is tricky. In Markdown special characters (`-`, `#` etc.) can be escaped (`\-`, `\#` etc.) so that - // they're parsed as plain text. Currently, whether a character was escaped or not is not recorded in the Node, - // so here we don't know. If we just wrote out those characters unescaped, the resulting Markdown would change - // meaning (turn into a list item, heading, etc.). - // You might say "Why not store that in the Node when parsing", but that wouldn't work for the use case where - // nodes are constructed directly instead of via parsing. This renderer needs to work for that too. - // So currently, when in doubt, we escape. For special characters only occurring at the beginning of a line, - // we only escape them then (we wouldn't want to escape every `.` for example). + // Text is tricky. In Markdown special characters (`-`, `#` etc.) can be escaped (`\-`, `\#` + // etc.) so that they're parsed as plain text. Currently, whether a character was escaped or + // not is not recorded in the Node, so here we don't know. If we just wrote out those + // characters unescaped, the resulting Markdown would change meaning (turn into a list item, + // heading, etc.). + // + // You might say "Why not store that in the Node when parsing", but that wouldn't work for + // the use case where nodes are constructed directly instead of via parsing. This renderer + // needs to work for that too. + // + // So currently, when in doubt, we escape. For special characters only occurring at the + // beginning of a line, we only escape them then (we wouldn't want to escape every `.` for + // example). String literal = text.getLiteral(); if (writer.isAtLineStart() && !literal.isEmpty()) { char c = literal.charAt(0); switch (c) { - case '-': { - // Would be ambiguous with a bullet list marker, escape - writer.raw("\\-"); - literal = literal.substring(1); - break; - } - case '#': { - // Would be ambiguous with an ATX heading, escape - writer.raw("\\#"); - literal = literal.substring(1); - break; - } - case '=': { - // Would be ambiguous with a Setext heading, escape unless it's the first line in the block - if (text.getPrevious() != null) { - writer.raw("\\="); + case '-': + { + // Would be ambiguous with a bullet list marker, escape + writer.raw("\\-"); literal = literal.substring(1); + break; + } + case '#': + { + // Would be ambiguous with an ATX heading, escape + writer.raw("\\#"); + literal = literal.substring(1); + break; + } + case '=': + { + // Would be ambiguous with a Setext heading, escape unless it's the first + // line in the block + if (text.getPrevious() != null) { + writer.raw("\\="); + literal = literal.substring(1); + } + break; } - break; - } case '0': case '1': case '2': @@ -386,26 +414,29 @@ public void visit(Text text) { case '6': case '7': case '8': - case '9': { - // Check for ordered list marker - Matcher m = orderedListMarkerPattern.matcher(literal); - if (m.find()) { - writer.raw(m.group(1)); - writer.raw("\\" + m.group(2)); - literal = literal.substring(m.end()); + case '9': + { + // Check for ordered list marker + Matcher m = orderedListMarkerPattern.matcher(literal); + if (m.find()) { + writer.raw(m.group(1)); + writer.raw("\\" + m.group(2)); + literal = literal.substring(m.end()); + } + break; + } + case '\t': + { + writer.raw(" "); + literal = literal.substring(1); + break; + } + case ' ': + { + writer.raw(" "); + literal = literal.substring(1); + break; } - break; - } - case '\t': { - writer.raw(" "); - literal = literal.substring(1); - break; - } - case ' ': { - writer.raw(" "); - literal = literal.substring(1); - break; - } } } @@ -467,13 +498,13 @@ private static String repeat(String s, int count) { } private static List getLines(String literal) { - // Without -1, split would discard all trailing empty strings, which is not what we want, e.g. it would - // return the same result for "abc", "abc\n" and "abc\n\n". + // Without -1, split would discard all trailing empty strings, which is not what we want, + // e.g. it would return the same result for "abc", "abc\n" and "abc\n\n". // With -1, it returns ["abc"], ["abc", ""] and ["abc", "", ""]. String[] parts = literal.split("\n", -1); if (parts[parts.length - 1].isEmpty()) { - // But we don't want the last empty string, as "\n" is used as a line terminator (not a separator), - // so return without the last element. + // But we don't want the last empty string, as "\n" is used as a line terminator (not a + // separator), so return without the last element. return List.of(parts).subList(0, parts.length - 1); } else { return List.of(parts); @@ -524,14 +555,18 @@ private static class OrderedListHolder extends ListHolder { protected OrderedListHolder(ListHolder parent, OrderedList orderedList) { super(parent); - delimiter = orderedList.getMarkerDelimiter() != null ? orderedList.getMarkerDelimiter() : "."; - number = orderedList.getMarkerStartNumber() != null ? orderedList.getMarkerStartNumber() : 1; + delimiter = + orderedList.getMarkerDelimiter() != null + ? orderedList.getMarkerDelimiter() + : "."; + number = + orderedList.getMarkerStartNumber() != null + ? orderedList.getMarkerStartNumber() + : 1; } } - /** - * Visits nodes to check if there are any soft or hard line breaks. - */ + /** Visits nodes to check if there are any soft or hard line breaks. */ private static class LineBreakVisitor extends AbstractVisitor { private boolean lineBreak = false; diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java index 40640d1b4..40c4fc5a9 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererContext.java @@ -1,8 +1,7 @@ package org.commonmark.renderer.markdown; -import org.commonmark.node.Node; - import java.util.Set; +import org.commonmark.node.Node; /** * Context that is passed to custom node renderers, see {@link MarkdownNodeRendererFactory#create}. @@ -15,16 +14,17 @@ public interface MarkdownNodeRendererContext { MarkdownWriter getWriter(); /** - * Render the specified node and its children using the configured renderers. This should be used to render child - * nodes; be careful not to pass the node that is being rendered, that would result in an endless loop. + * Render the specified node and its children using the configured renderers. This should be + * used to render child nodes; be careful not to pass the node that is being rendered, that + * would result in an endless loop. * * @param node the node to render */ void render(Node node); /** - * @return additional special characters that need to be escaped if they occur in normal text; currently only ASCII - * characters are allowed + * @return additional special characters that need to be escaped if they occur in normal text; + * currently only ASCII characters are allowed */ Set getSpecialCharacters(); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java index 14221ea56..d0bba666b 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownNodeRendererFactory.java @@ -1,12 +1,9 @@ package org.commonmark.renderer.markdown; -import org.commonmark.renderer.NodeRenderer; - import java.util.Set; +import org.commonmark.renderer.NodeRenderer; -/** - * Factory for instantiating new node renderers for rendering custom nodes. - */ +/** Factory for instantiating new node renderers for rendering custom nodes. */ public interface MarkdownNodeRendererFactory { /** @@ -18,8 +15,8 @@ public interface MarkdownNodeRendererFactory { NodeRenderer create(MarkdownNodeRendererContext context); /** - * @return the additional special characters that this factory would like to have escaped in normal text; currently - * only ASCII characters are allowed + * @return the additional special characters that this factory would like to have escaped in + * normal text; currently only ASCII characters are allowed */ Set getSpecialCharacters(); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java index e4996fb08..1acde9a55 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java @@ -1,26 +1,29 @@ package org.commonmark.renderer.markdown; +import java.util.*; import org.commonmark.Extension; import org.commonmark.internal.renderer.NodeRendererMap; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.Renderer; -import java.util.*; - /** * Renders nodes to Markdown (CommonMark syntax); use {@link #builder()} to create a renderer. - *

    - * Note that it doesn't currently preserve the exact syntax of the original input Markdown (if any): + * + *

    Note that it doesn't currently preserve the exact syntax of the original input Markdown (if + * any): + * *

      - *
    • Headings are output as ATX headings if possible (multi-line headings need Setext headings)
    • - *
    • Links are always rendered as inline links (no support for reference links yet)
    • - *
    • Escaping might be over-eager, e.g. a plain {@code *} might be escaped - * even though it doesn't need to be in that particular context
    • - *
    • Leading whitespace in paragraphs is not preserved
    • + *
    • Headings are output as ATX headings if possible (multi-line headings need Setext headings) + *
    • Links are always rendered as inline links (no support for reference links yet) + *
    • Escaping might be over-eager, e.g. a plain {@code *} might be escaped even though it + * doesn't need to be in that particular context + *
    • Leading whitespace in paragraphs is not preserved *
    - * However, it should produce Markdown that is semantically equivalent to the input, i.e. if the Markdown was parsed - * again and compared against the original AST, it should be the same (minus bugs). + * + * However, it should produce Markdown that is semantically equivalent to the input, i.e. if the + * Markdown was parsed again and compared against the original AST, it should be the same (minus + * bugs). */ public class MarkdownRenderer implements Renderer { @@ -30,17 +33,18 @@ private MarkdownRenderer(Builder builder) { this.nodeRendererFactories = new ArrayList<>(builder.nodeRendererFactories.size() + 1); this.nodeRendererFactories.addAll(builder.nodeRendererFactories); // Add as last. This means clients can override the rendering of core nodes if they want. - this.nodeRendererFactories.add(new MarkdownNodeRendererFactory() { - @Override - public NodeRenderer create(MarkdownNodeRendererContext context) { - return new CoreMarkdownNodeRenderer(context); - } - - @Override - public Set getSpecialCharacters() { - return Set.of(); - } - }); + this.nodeRendererFactories.add( + new MarkdownNodeRendererFactory() { + @Override + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new CoreMarkdownNodeRenderer(context); + } + + @Override + public Set getSpecialCharacters() { + return Set.of(); + } + }); } /** @@ -80,11 +84,12 @@ public MarkdownRenderer build() { } /** - * Add a factory for instantiating a node renderer (done when rendering). This allows to override the rendering - * of node types or define rendering for custom node types. - *

    - * If multiple node renderers for the same node type are created, the one from the factory that was added first - * "wins". (This is how the rendering for core node types can be overridden; the default rendering comes last.) + * Add a factory for instantiating a node renderer (done when rendering). This allows to + * override the rendering of node types or define rendering for custom node types. + * + *

    If multiple node renderers for the same node type are created, the one from the + * factory that was added first "wins". (This is how the rendering for core node types can + * be overridden; the default rendering comes last.) * * @param nodeRendererFactory the factory for creating a node renderer * @return {@code this} @@ -101,7 +106,8 @@ public Builder nodeRendererFactory(MarkdownNodeRendererFactory nodeRendererFacto public Builder extensions(Iterable extensions) { for (Extension extension : extensions) { if (extension instanceof MarkdownRendererExtension) { - MarkdownRendererExtension markdownRendererExtension = (MarkdownRendererExtension) extension; + MarkdownRendererExtension markdownRendererExtension = + (MarkdownRendererExtension) extension; markdownRendererExtension.extend(this); } } @@ -109,13 +115,12 @@ public Builder extensions(Iterable extensions) { } } - /** - * Extension for {@link MarkdownRenderer} for rendering custom nodes. - */ + /** Extension for {@link MarkdownRenderer} for rendering custom nodes. */ public interface MarkdownRendererExtension extends Extension { /** - * Extend Markdown rendering, usually by registering custom node renderers using {@link Builder#nodeRendererFactory}. + * Extend Markdown rendering, usually by registering custom node renderers using {@link + * Builder#nodeRendererFactory}. * * @param rendererBuilder the renderer builder to extend */ diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index c9f427021..074a8e939 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -1,13 +1,10 @@ package org.commonmark.renderer.markdown; -import org.commonmark.text.CharMatcher; - import java.io.IOException; import java.util.LinkedList; +import org.commonmark.text.CharMatcher; -/** - * Writer for Markdown (CommonMark) text. - */ +/** Writer for Markdown (CommonMark) text. */ public class MarkdownWriter { private final Appendable buffer; @@ -16,8 +13,9 @@ public class MarkdownWriter { private char lastChar; private boolean atLineStart = true; - // Stacks of settings that affect various rendering behaviors. The common pattern here is that callers use "push" to - // change a setting, render some nodes, and then "pop" the setting off the stack again to restore previous state. + // Stacks of settings that affect various rendering behaviors. The common pattern here is that + // callers use "push" to change a setting, render some nodes, and then "pop" the setting off the + // stack again to restore previous state. private final LinkedList prefixes = new LinkedList<>(); private final LinkedList tight = new LinkedList<>(); private final LinkedList rawEscapes = new LinkedList<>(); @@ -26,17 +24,13 @@ public MarkdownWriter(Appendable out) { buffer = out; } - /** - * Write the supplied string (raw/unescaped except if {@link #pushRawEscape} was used). - */ + /** Write the supplied string (raw/unescaped except if {@link #pushRawEscape} was used). */ public void raw(String s) { flushBlockSeparator(); write(s, null); } - /** - * Write the supplied character (raw/unescaped except if {@link #pushRawEscape} was used). - */ + /** Write the supplied character (raw/unescaped except if {@link #pushRawEscape} was used). */ public void raw(char c) { flushBlockSeparator(); write(c); @@ -45,7 +39,7 @@ public void raw(char c) { /** * Write the supplied string with escaping. * - * @param s the string to write + * @param s the string to write * @param escape which characters to escape */ public void text(String s, CharMatcher escape) { @@ -59,9 +53,7 @@ public void text(String s, CharMatcher escape) { atLineStart = false; } - /** - * Write a newline (line terminator). - */ + /** Write a newline (line terminator). */ public void line() { write('\n'); writePrefixes(); @@ -69,19 +61,20 @@ public void line() { } /** - * Enqueue a block separator to be written before the next text is written. Block separators are not written - * straight away because if there are no more blocks to write we don't want a separator (at the end of the document). + * Enqueue a block separator to be written before the next text is written. Block separators are + * not written straight away because if there are no more blocks to write we don't want a + * separator (at the end of the document). */ public void block() { - // Remember whether this should be a tight or loose separator now because tight could get changed in between - // this and the next flush. + // Remember whether this should be a tight or loose separator now because tight could get + // changed in between this and the next flush. blockSeparator = isTight() ? 1 : 2; atLineStart = true; } /** - * Push a prefix onto the top of the stack. All prefixes are written at the beginning of each line, until the - * prefix is popped again. + * Push a prefix onto the top of the stack. All prefixes are written at the beginning of each + * line, until the prefix is popped again. * * @param prefix the raw prefix string */ @@ -100,36 +93,32 @@ public void writePrefix(String prefix) { atLineStart = tmp; } - /** - * Remove the last prefix from the top of the stack. - */ + /** Remove the last prefix from the top of the stack. */ public void popPrefix() { prefixes.removeLast(); } /** - * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight - * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines - * within the list. - *

    - * Note that changing this does not affect block separators that have already been enqueued with {@link #block()}, - * only future ones. + * Change whether blocks are tight or loose. Loose is the default where blocks are separated by + * a blank line. Tight is where blocks are not separated by a blank line. Tight blocks are used + * in lists, if there are no blank lines within the list. + * + *

    Note that changing this does not affect block separators that have already been enqueued + * with {@link #block()}, only future ones. */ public void pushTight(boolean tight) { this.tight.addLast(tight); } - /** - * Remove the last "tight" setting from the top of the stack. - */ + /** Remove the last "tight" setting from the top of the stack. */ public void popTight() { this.tight.removeLast(); } /** - * Escape the characters matching the supplied matcher, in all text (text and raw). This might be useful to - * extensions that add another layer of syntax, e.g. the tables extension that uses `|` to separate cells and needs - * all `|` characters to be escaped (even in code spans). + * Escape the characters matching the supplied matcher, in all text (text and raw). This might + * be useful to extensions that add another layer of syntax, e.g. the tables extension that uses + * `|` to separate cells and needs all `|` characters to be escaped (even in code spans). * * @param rawEscape the characters to escape in raw text */ @@ -137,9 +126,7 @@ public void pushRawEscape(CharMatcher rawEscape) { rawEscapes.add(rawEscape); } - /** - * Remove the last raw escape from the top of the stack. - */ + /** Remove the last raw escape from the top of the stack. */ public void popRawEscape() { rawEscapes.removeLast(); } @@ -152,7 +139,8 @@ public char getLastChar() { } /** - * @return whether we're at the line start (not counting any prefixes), i.e. after a {@link #line} or {@link #block}. + * @return whether we're at the line start (not counting any prefixes), i.e. after a {@link + * #line} or {@link #block}. */ public boolean isAtLineStart() { return atLineStart; @@ -199,7 +187,8 @@ private void writePrefixes() { } /** - * If a block separator has been enqueued with {@link #block()} but not yet written, write it now. + * If a block separator has been enqueued with {@link #block()} but not yet written, write it + * now. */ private void flushBlockSeparator() { if (blockSeparator != 0) { diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/package-info.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/package-info.java index f707671d5..8b6b9a464 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/package-info.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/package-info.java @@ -1,4 +1,2 @@ -/** - * Markdown rendering (see {@link org.commonmark.renderer.markdown.MarkdownRenderer}) - */ +/** Markdown rendering (see {@link org.commonmark.renderer.markdown.MarkdownRenderer}) */ package org.commonmark.renderer.markdown; diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java index 62a1a054d..22718d088 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -1,10 +1,9 @@ package org.commonmark.renderer.text; +import java.util.Set; import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; -import java.util.Set; - /** * The node renderer that renders all the core nodes (comes last in the order of node renderers). */ @@ -42,8 +41,7 @@ public Set> getNodeTypes() { Code.class, HtmlInline.class, SoftLineBreak.class, - HardLineBreak.class - ); + HardLineBreak.class); } @Override diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/LineBreakRendering.java b/commonmark/src/main/java/org/commonmark/renderer/text/LineBreakRendering.java index 27eeaf0da..cff8c2086 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/LineBreakRendering.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/LineBreakRendering.java @@ -1,19 +1,14 @@ package org.commonmark.renderer.text; -/** - * Control how line breaks are rendered. - */ +/** Control how line breaks are rendered. */ public enum LineBreakRendering { /** - * Strip all line breaks within blocks and between blocks, resulting in all the text in a single line. + * Strip all line breaks within blocks and between blocks, resulting in all the text in a single + * line. */ STRIP, - /** - * Use single line breaks between blocks, not a blank line (also render all lists as tight). - */ + /** Use single line breaks between blocks, not a blank line (also render all lists as tight). */ COMPACT, - /** - * Separate blocks by a blank line (and respect tight vs loose lists). - */ + /** Separate blocks by a blank line (and respect tight vs loose lists). */ SEPARATE_BLOCKS, } diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java index d6fcb8d77..fbed71f97 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java @@ -4,14 +4,12 @@ public interface TextContentNodeRendererContext { - /** - * Controls how line breaks should be rendered, see {@link LineBreakRendering}. - */ + /** Controls how line breaks should be rendered, see {@link LineBreakRendering}. */ LineBreakRendering lineBreakRendering(); /** - * @return true for stripping new lines and render text as "single line", - * false for keeping all line breaks. + * @return true for stripping new lines and render text as "single line", false for keeping all + * line breaks. * @deprecated Use {@link #lineBreakRendering()} instead */ @Deprecated @@ -23,8 +21,9 @@ public interface TextContentNodeRendererContext { TextContentWriter getWriter(); /** - * Render the specified node and its children using the configured renderers. This should be used to render child - * nodes; be careful not to pass the node that is being rendered, that would result in an endless loop. + * Render the specified node and its children using the configured renderers. This should be + * used to render child nodes; be careful not to pass the node that is being rendered, that + * would result in an endless loop. * * @param node the node to render */ diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererFactory.java index bf193dff4..bf471fb34 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererFactory.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererFactory.java @@ -2,9 +2,7 @@ import org.commonmark.renderer.NodeRenderer; -/** - * Factory for instantiating new node renderers when rendering is done. - */ +/** Factory for instantiating new node renderers when rendering is done. */ public interface TextContentNodeRendererFactory { /** diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java index 9a8bcc997..4a7d2d4bc 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java @@ -1,16 +1,13 @@ package org.commonmark.renderer.text; +import java.util.ArrayList; +import java.util.List; import org.commonmark.Extension; import org.commonmark.internal.renderer.NodeRendererMap; import org.commonmark.node.Node; import org.commonmark.renderer.Renderer; -import java.util.ArrayList; -import java.util.List; - -/** - * Renders nodes to plain text content with minimal markup-like additions. - */ +/** Renders nodes to plain text content with minimal markup-like additions. */ public class TextContentRenderer implements Renderer { private final LineBreakRendering lineBreakRendering; @@ -37,7 +34,8 @@ public static Builder builder() { @Override public void render(Node node, Appendable output) { - RendererContext context = new RendererContext(new TextContentWriter(output, lineBreakRendering)); + RendererContext context = + new RendererContext(new TextContentWriter(output, lineBreakRendering)); context.render(node); } @@ -64,8 +62,8 @@ public TextContentRenderer build() { } /** - * Configure how line breaks (newlines) are rendered, see {@link LineBreakRendering}. - * The default is {@link LineBreakRendering#COMPACT}. + * Configure how line breaks (newlines) are rendered, see {@link LineBreakRendering}. The + * default is {@link LineBreakRendering#COMPACT}. * * @param lineBreakRendering the mode to use * @return {@code this} @@ -78,23 +76,26 @@ public Builder lineBreakRendering(LineBreakRendering lineBreakRendering) { /** * Set the value of flag for stripping new lines. * - * @param stripNewlines true for stripping new lines and render text as "single line", - * false for keeping all line breaks + * @param stripNewlines true for stripping new lines and render text as "single line", false + * for keeping all line breaks * @return {@code this} - * @deprecated Use {@link #lineBreakRendering(LineBreakRendering)} with {@link LineBreakRendering#STRIP} instead + * @deprecated Use {@link #lineBreakRendering(LineBreakRendering)} with {@link + * LineBreakRendering#STRIP} instead */ @Deprecated public Builder stripNewlines(boolean stripNewlines) { - this.lineBreakRendering = stripNewlines ? LineBreakRendering.STRIP : LineBreakRendering.COMPACT; + this.lineBreakRendering = + stripNewlines ? LineBreakRendering.STRIP : LineBreakRendering.COMPACT; return this; } /** - * Add a factory for instantiating a node renderer (done when rendering). This allows to override the rendering - * of node types or define rendering for custom node types. - *

    - * If multiple node renderers for the same node type are created, the one from the factory that was added first - * "wins". (This is how the rendering for core node types can be overridden; the default rendering comes last.) + * Add a factory for instantiating a node renderer (done when rendering). This allows to + * override the rendering of node types or define rendering for custom node types. + * + *

    If multiple node renderers for the same node type are created, the one from the + * factory that was added first "wins". (This is how the rendering for core node types can + * be overridden; the default rendering comes last.) * * @param nodeRendererFactory the factory for creating a node renderer * @return {@code this} @@ -120,9 +121,7 @@ public Builder extensions(Iterable extensions) { } } - /** - * Extension for {@link TextContentRenderer}. - */ + /** Extension for {@link TextContentRenderer}. */ public interface TextContentRendererExtension extends Extension { void extend(TextContentRenderer.Builder rendererBuilder); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java index 1fb482785..e4cdfe373 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java @@ -41,8 +41,13 @@ public void line() { } public void block() { - blockSeparator = lineBreakRendering == LineBreakRendering.STRIP ? " " : // - lineBreakRendering == LineBreakRendering.COMPACT || isTight() ? "\n" : "\n\n"; + blockSeparator = + lineBreakRendering == LineBreakRendering.STRIP + ? " " + : // + lineBreakRendering == LineBreakRendering.COMPACT || isTight() + ? "\n" + : "\n\n"; } public void resetBlock() { @@ -64,8 +69,8 @@ public void write(char c) { } /** - * Push a prefix onto the top of the stack. All prefixes are written at the beginning of each line, until the - * prefix is popped again. + * Push a prefix onto the top of the stack. All prefixes are written at the beginning of each + * line, until the prefix is popped again. * * @param prefix the raw prefix string */ @@ -82,28 +87,24 @@ public void writePrefix(String prefix) { write(prefix); } - /** - * Remove the last prefix from the top of the stack. - */ + /** Remove the last prefix from the top of the stack. */ public void popPrefix() { prefixes.removeLast(); } /** - * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight - * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines - * within the list. - *

    - * Note that changing this does not affect block separators that have already been enqueued with {@link #block()}, - * only future ones. + * Change whether blocks are tight or loose. Loose is the default where blocks are separated by + * a blank line. Tight is where blocks are not separated by a blank line. Tight blocks are used + * in lists, if there are no blank lines within the list. + * + *

    Note that changing this does not affect block separators that have already been enqueued + * with {@link #block()}, only future ones. */ public void pushTight(boolean tight) { this.tight.addLast(tight); } - /** - * Remove the last "tight" setting from the top of the stack. - */ + /** Remove the last "tight" setting from the top of the stack. */ public void popTight() { this.tight.removeLast(); } @@ -119,7 +120,8 @@ private void writePrefixes() { } /** - * If a block separator has been enqueued with {@link #block()} but not yet written, write it now. + * If a block separator has been enqueued with {@link #block()} but not yet written, write it + * now. */ private void flushBlockSeparator() { if (blockSeparator != null) { diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/package-info.java b/commonmark/src/main/java/org/commonmark/renderer/text/package-info.java index 8309f4bd6..06b4fa31f 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/package-info.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/package-info.java @@ -1,4 +1,5 @@ /** - * Plain text rendering with minimal markup (see {@link org.commonmark.renderer.text.TextContentRenderer}) + * Plain text rendering with minimal markup (see {@link + * org.commonmark.renderer.text.TextContentRenderer}) */ package org.commonmark.renderer.text; diff --git a/commonmark/src/main/java/org/commonmark/text/AsciiMatcher.java b/commonmark/src/main/java/org/commonmark/text/AsciiMatcher.java index 0d9cea458..b3e3b7651 100644 --- a/commonmark/src/main/java/org/commonmark/text/AsciiMatcher.java +++ b/commonmark/src/main/java/org/commonmark/text/AsciiMatcher.java @@ -3,9 +3,7 @@ import java.util.BitSet; import java.util.Set; -/** - * Char matcher that can match ASCII characters efficiently. - */ +/** Char matcher that can match ASCII characters efficiently. */ public class AsciiMatcher implements CharMatcher { private final BitSet set; diff --git a/commonmark/src/main/java/org/commonmark/text/CharMatcher.java b/commonmark/src/main/java/org/commonmark/text/CharMatcher.java index 2833e65c3..6143258a8 100644 --- a/commonmark/src/main/java/org/commonmark/text/CharMatcher.java +++ b/commonmark/src/main/java/org/commonmark/text/CharMatcher.java @@ -2,10 +2,10 @@ /** * Matcher interface for {@code char} values. - *

    - * Note that because this matches on {@code char} values only (as opposed to {@code int} code points), - * this only operates on the level of code units and doesn't support supplementary characters - * (see {@link Character#isSupplementaryCodePoint(int)}). + * + *

    Note that because this matches on {@code char} values only (as opposed to {@code int} code + * points), this only operates on the level of code units and doesn't support supplementary + * characters (see {@link Character#isSupplementaryCodePoint(int)}). */ public interface CharMatcher { diff --git a/commonmark/src/main/java/org/commonmark/text/Characters.java b/commonmark/src/main/java/org/commonmark/text/Characters.java index ee56ca67e..5832dd6cf 100644 --- a/commonmark/src/main/java/org/commonmark/text/Characters.java +++ b/commonmark/src/main/java/org/commonmark/text/Characters.java @@ -1,8 +1,6 @@ package org.commonmark.text; -/** - * Functions for finding characters in strings or checking characters. - */ +/** Functions for finding characters in strings or checking characters. */ public class Characters { public static int find(char c, CharSequence s, int startIndex) { @@ -57,7 +55,8 @@ public static boolean isSpaceOrTab(CharSequence s, int index) { } /** - * @see Unicode punctuation character + * @see Unicode + * punctuation character */ public static boolean isPunctuationCodePoint(int codePoint) { switch (Character.getType(codePoint)) { @@ -69,7 +68,7 @@ public static boolean isPunctuationCodePoint(int codePoint) { case Character.OTHER_PUNCTUATION: case Character.INITIAL_QUOTE_PUNCTUATION: case Character.FINAL_QUOTE_PUNCTUATION: - // General category "S" (symbol) + // General category "S" (symbol) case Character.MATH_SYMBOL: case Character.CURRENCY_SYMBOL: case Character.MODIFIER_SYMBOL: @@ -94,9 +93,11 @@ public static boolean isPunctuationCodePoint(int codePoint) { } /** - * Check whether the provided code point is a Unicode whitespace character as defined in the spec. + * Check whether the provided code point is a Unicode whitespace character as defined in the + * spec. * - * @see Unicode whitespace character + * @see Unicode + * whitespace character */ public static boolean isWhitespaceCodePoint(int codePoint) { switch (codePoint) { diff --git a/commonmark/src/main/java/org/commonmark/text/package-info.java b/commonmark/src/main/java/org/commonmark/text/package-info.java index ab9eec6f1..6e27fda21 100644 --- a/commonmark/src/main/java/org/commonmark/text/package-info.java +++ b/commonmark/src/main/java/org/commonmark/text/package-info.java @@ -1,4 +1,2 @@ -/** - * Text processing utilities for parsing and rendering, exported for use by extensions - */ +/** Text processing utilities for parsing and rendering, exported for use by extensions */ package org.commonmark.text; diff --git a/commonmark/src/test/java/org/commonmark/ProfilingMain.java b/commonmark/src/test/java/org/commonmark/ProfilingMain.java index 83b1bdaff..15e38bcde 100644 --- a/commonmark/src/test/java/org/commonmark/ProfilingMain.java +++ b/commonmark/src/test/java/org/commonmark/ProfilingMain.java @@ -1,17 +1,17 @@ package org.commonmark; +import java.util.ArrayList; +import java.util.List; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.TestResources; -import java.util.ArrayList; -import java.util.List; - public class ProfilingMain { private static final String SPEC = TestResources.readAsString(TestResources.getSpec()); - // private static final List SPEC_EXAMPLES = ExampleReader.readExampleSources(TestResources.getSpec()); + // private static final List SPEC_EXAMPLES = + // ExampleReader.readExampleSources(TestResources.getSpec()); private static final Parser PARSER = Parser.builder().build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); diff --git a/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java b/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java index a834665ff..815107523 100644 --- a/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java @@ -1,31 +1,41 @@ package org.commonmark.internal; -import org.commonmark.node.*; -import org.commonmark.parser.block.BlockParserFactory; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.HashSet; import java.util.List; import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; +import org.commonmark.node.*; +import org.commonmark.parser.block.BlockParserFactory; +import org.junit.jupiter.api.Test; class DocumentParserTest { - private static final List CORE_FACTORIES = List.of( - new BlockQuoteParser.Factory(), - new HeadingParser.Factory(), - new FencedCodeBlockParser.Factory(), - new HtmlBlockParser.Factory(), - new ThematicBreakParser.Factory(), - new ListBlockParser.Factory(), - new IndentedCodeBlockParser.Factory()); + private static final List CORE_FACTORIES = + List.of( + new BlockQuoteParser.Factory(), + new HeadingParser.Factory(), + new FencedCodeBlockParser.Factory(), + new HtmlBlockParser.Factory(), + new ThematicBreakParser.Factory(), + new ListBlockParser.Factory(), + new IndentedCodeBlockParser.Factory()); @Test void calculateBlockParserFactories_givenAFullListOfAllowedNodes_includesAllCoreFactories() { List customParserFactories = List.of(); - var enabledBlockTypes = Set.of(BlockQuote.class, Heading.class, FencedCodeBlock.class, HtmlBlock.class, ThematicBreak.class, ListBlock.class, IndentedCodeBlock.class); + var enabledBlockTypes = + Set.of( + BlockQuote.class, + Heading.class, + FencedCodeBlock.class, + HtmlBlock.class, + ThematicBreak.class, + ListBlock.class, + IndentedCodeBlock.class); - List blockParserFactories = DocumentParser.calculateBlockParserFactories(customParserFactories, enabledBlockTypes); + List blockParserFactories = + DocumentParser.calculateBlockParserFactories( + customParserFactories, enabledBlockTypes); assertThat(blockParserFactories).hasSameSizeAs(CORE_FACTORIES); for (BlockParserFactory factory : CORE_FACTORIES) { @@ -39,13 +49,17 @@ void calculateBlockParserFactories_givenAListOfAllowedNodes_includesAssociatedFa Set> nodes = new HashSet<>(); nodes.add(IndentedCodeBlock.class); - List blockParserFactories = DocumentParser.calculateBlockParserFactories(customParserFactories, nodes); + List blockParserFactories = + DocumentParser.calculateBlockParserFactories(customParserFactories, nodes); assertThat(blockParserFactories).hasSize(1); - assertThat(hasInstance(blockParserFactories, IndentedCodeBlockParser.Factory.class)).isTrue(); + assertThat(hasInstance(blockParserFactories, IndentedCodeBlockParser.Factory.class)) + .isTrue(); } - private boolean hasInstance(List blockParserFactories, Class factoryClass) { + private boolean hasInstance( + List blockParserFactories, + Class factoryClass) { for (BlockParserFactory factory : blockParserFactories) { if (factory.getClass().equals(factoryClass)) { return true; @@ -53,5 +67,4 @@ private boolean hasInstance(List blockParserFactories, Class } return false; } - } diff --git a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java index b69ada0e9..d9ee3a990 100644 --- a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java @@ -1,12 +1,12 @@ package org.commonmark.internal; +import static org.assertj.core.api.Assertions.assertThat; + import org.commonmark.internal.LinkReferenceDefinitionParser.State; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.SourceLine; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - class LinkReferenceDefinitionParserTest { private final LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); @@ -149,8 +149,8 @@ void testTitleMultiline2() { void testTitleMultiline3() { parse("[foo]: /url"); assertThat(parser.getState()).isEqualTo(State.START_TITLE); - // Note that this looks like a valid title until we parse "bad", at which point we need to treat the whole line - // as a paragraph line and discard any already parsed title. + // Note that this looks like a valid title until we parse "bad", at which point we need to + // treat the whole line as a paragraph line and discard any already parsed title. parse("\"title\" bad"); assertThat(parser.getState()).isEqualTo(State.PARAGRAPH); @@ -192,13 +192,15 @@ private static void assertState(String input, State state, String paragraphConte assertParagraphLines(paragraphContent, parser); } - private static void assertDef(LinkReferenceDefinition def, String label, String destination, String title) { + private static void assertDef( + LinkReferenceDefinition def, String label, String destination, String title) { assertThat(def.getLabel()).isEqualTo(label); assertThat(def.getDestination()).isEqualTo(destination); assertThat(def.getTitle()).isEqualTo(title); } - private static void assertParagraphLines(String expectedContent, LinkReferenceDefinitionParser parser) { + private static void assertParagraphLines( + String expectedContent, LinkReferenceDefinitionParser parser) { String actual = parser.getParagraphLines().getContent(); assertThat(actual).isEqualTo(expectedContent); } diff --git a/commonmark/src/test/java/org/commonmark/internal/util/EscapingTest.java b/commonmark/src/test/java/org/commonmark/internal/util/EscapingTest.java index eb2f1a801..dffe25be9 100644 --- a/commonmark/src/test/java/org/commonmark/internal/util/EscapingTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/util/EscapingTest.java @@ -1,9 +1,9 @@ package org.commonmark.internal.util; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; + class EscapingTest { @Test diff --git a/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java b/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java index 7d0e0beb7..cacc8e579 100644 --- a/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/util/LineReaderTest.java @@ -1,17 +1,16 @@ package org.commonmark.internal.util; -import org.junit.jupiter.api.Test; +import static java.util.stream.Collectors.joining; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.commonmark.internal.util.LineReader.CHAR_BUFFER_SIZE; import java.io.*; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; - -import static java.util.stream.Collectors.joining; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.commonmark.internal.util.LineReader.CHAR_BUFFER_SIZE; +import org.junit.jupiter.api.Test; class LineReaderTest { @@ -45,12 +44,16 @@ void testReadLine() throws IOException { assertLines(repeat("a", CHAR_BUFFER_SIZE) + "b", "\r"); assertLines("", "\n", "", "\r", "", "\r\n", "", "\n"); - assertLines("what", "\r", "are", "\r", "", "\r", "you", "\r\n", "", "\r\n", "even", "\n", "doing", null); + assertLines( + "what", "\r", "are", "\r", "", "\r", "you", "\r\n", "", "\r\n", "even", "\n", + "doing", null); } @Test void testClose() throws IOException { - var reader = new InputStreamReader(new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8))); + var reader = + new InputStreamReader( + new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8))); var lineReader = new LineReader(reader); lineReader.close(); lineReader.close(); @@ -58,7 +61,9 @@ void testClose() throws IOException { } private void assertLines(String... s) throws IOException { - assertThat(s.length).as("Expected parts needs to be even (pairs of content and terminator)").isEven(); + assertThat(s.length) + .as("Expected parts needs to be even (pairs of content and terminator)") + .isEven(); var input = Arrays.stream(s).filter(Objects::nonNull).collect(joining("")); assertLines(new StringReader(input), s); @@ -86,9 +91,7 @@ private static String repeat(String s, int count) { return sb.toString(); } - /** - * Reader that only reads 0 or 1 chars at a time to test the corner cases. - */ + /** Reader that only reads 0 or 1 chars at a time to test the corner cases. */ private static class SlowStringReader extends Reader { private final String s; @@ -118,7 +121,6 @@ public int read(char[] cbuf, int off, int len) { } @Override - public void close() { - } + public void close() {} } } diff --git a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java index d0f45a6bc..167f74f25 100644 --- a/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/InlineContentParserTest.java @@ -1,5 +1,8 @@ package org.commonmark.parser; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; import org.commonmark.node.*; import org.commonmark.parser.beta.InlineContentParser; import org.commonmark.parser.beta.InlineContentParserFactory; @@ -8,15 +11,14 @@ import org.commonmark.test.Nodes; import org.junit.jupiter.api.Test; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - class InlineContentParserTest { @Test void customInlineContentParser() { - var parser = Parser.builder().customInlineContentParserFactory(new DollarInlineParser.Factory()).build(); + var parser = + Parser.builder() + .customInlineContentParserFactory(new DollarInlineParser.Factory()) + .build(); var doc = parser.parse("Test: $hey *there*$ $you$\n\n# Heading $heading$\n"); var inline1 = Nodes.find(doc, DollarInline.class); assertThat(inline1.getLiteral()).isEqualTo("hey *there*"); @@ -28,7 +30,8 @@ void customInlineContentParser() { var inline3 = (DollarInline) heading.getLastChild(); assertThat(inline3.getLiteral()).isEqualTo("heading"); - // Parser is created for each inline snippet, which is why the index resets for the second snippet. + // Parser is created for each inline snippet, which is why the index resets for the second + // snippet. assertThat(inline1.getIndex()).isEqualTo(0); assertThat(inline2.getIndex()).isEqualTo(1); assertThat(inline3.getIndex()).isEqualTo(0); @@ -37,8 +40,12 @@ void customInlineContentParser() { @Test void bangInlineContentParser() { // See if using ! for a custom inline content parser works. - // ![] is used for images, but if it's not followed by a [, it should be possible to parse it differently. - var parser = Parser.builder().customInlineContentParserFactory(new BangInlineParser.Factory()).build(); + // ![] is used for images, but if it's not followed by a [, it should be possible to parse + // it differently. + var parser = + Parser.builder() + .customInlineContentParserFactory(new BangInlineParser.Factory()) + .build(); var doc = parser.parse("![image](url) !notimage"); var image = Nodes.find(doc, Image.class); assertThat(image.getDestination()).isEqualTo("url"); @@ -98,8 +105,7 @@ public InlineContentParser create() { } } - private static class BangInline extends CustomNode { - } + private static class BangInline extends CustomNode {} private static class BangInlineParser implements InlineContentParser { diff --git a/commonmark/src/test/java/org/commonmark/parser/beta/LinkProcessorTest.java b/commonmark/src/test/java/org/commonmark/parser/beta/LinkProcessorTest.java index ef8739128..60dea4814 100644 --- a/commonmark/src/test/java/org/commonmark/parser/beta/LinkProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/beta/LinkProcessorTest.java @@ -1,21 +1,23 @@ package org.commonmark.parser.beta; +import static org.assertj.core.api.Assertions.assertThat; + import org.commonmark.node.Link; import org.commonmark.node.Text; import org.commonmark.parser.Parser; import org.commonmark.test.Nodes; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - class LinkProcessorTest { @Test void testLinkMarkerShouldNotBeIncludedByDefault() { - // If a link marker is registered but is not processed, the built-in link processor shouldn't consume it. - // And I think by default, other processors shouldn't consume it either (by accident). - // So requiring processors to opt into including the marker is better than requiring them to opt out, - // because processors that look for a marker already need to write some code to deal with the marker anyway, + // If a link marker is registered but is not processed, the built-in link processor + // shouldn't consume it. And I think by default, other processors shouldn't consume it + // either (by accident). + // So requiring processors to opt into including the marker is better + // than requiring them to opt out, because processors that look for a marker already need to + // write some code to deal with the marker anyway, // and will have tests ensuring that the marker is part of the parsed node, not the text. var parser = Parser.builder().linkMarker('^').build(); var doc = parser.parse("^[test](url)"); diff --git a/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java b/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java index bd74cab0e..437812866 100644 --- a/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java +++ b/commonmark/src/test/java/org/commonmark/parser/beta/ScannerTest.java @@ -1,21 +1,18 @@ package org.commonmark.parser.beta; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; import org.commonmark.node.SourceSpan; import org.commonmark.parser.SourceLine; import org.commonmark.parser.SourceLines; import org.junit.jupiter.api.Test; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - class ScannerTest { @Test void testNext() { - Scanner scanner = new Scanner(List.of( - SourceLine.of("foo bar", null)), - 0, 4); + Scanner scanner = new Scanner(List.of(SourceLine.of("foo bar", null)), 0, 4); assertThat(scanner.peek()).isEqualTo('b'); scanner.next(); assertThat(scanner.peek()).isEqualTo('a'); @@ -27,10 +24,8 @@ void testNext() { @Test void testMultipleLines() { - Scanner scanner = new Scanner(List.of( - SourceLine.of("ab", null), - SourceLine.of("cde", null)), - 0, 0); + Scanner scanner = + new Scanner(List.of(SourceLine.of("ab", null), SourceLine.of("cde", null)), 0, 0); assertThat(scanner.hasNext()).isTrue(); assertThat(scanner.peekPreviousCodePoint()).isEqualTo('\0'); assertThat(scanner.peek()).isEqualTo('a'); @@ -84,70 +79,77 @@ void testCodePoints() { @Test void testTextBetween() { - Scanner scanner = new Scanner(List.of( - SourceLine.of("ab", SourceSpan.of(10, 3, 13, 2)), - SourceLine.of("cde", SourceSpan.of(11, 4, 20, 3))), - 0, 0); + Scanner scanner = + new Scanner( + List.of( + SourceLine.of("ab", SourceSpan.of(10, 3, 13, 2)), + SourceLine.of("cde", SourceSpan.of(11, 4, 20, 3))), + 0, + 0); Position start = scanner.position(); scanner.next(); - assertSourceLines(scanner.getSource(start, scanner.position()), - "a", - SourceSpan.of(10, 3, 13, 1)); + assertSourceLines( + scanner.getSource(start, scanner.position()), "a", SourceSpan.of(10, 3, 13, 1)); Position afterA = scanner.position(); scanner.next(); - assertSourceLines(scanner.getSource(start, scanner.position()), - "ab", - SourceSpan.of(10, 3, 13, 2)); + assertSourceLines( + scanner.getSource(start, scanner.position()), "ab", SourceSpan.of(10, 3, 13, 2)); Position afterB = scanner.position(); scanner.next(); - assertSourceLines(scanner.getSource(start, scanner.position()), - "ab\n", - SourceSpan.of(10, 3, 13, 2)); + assertSourceLines( + scanner.getSource(start, scanner.position()), "ab\n", SourceSpan.of(10, 3, 13, 2)); scanner.next(); - assertSourceLines(scanner.getSource(start, scanner.position()), + assertSourceLines( + scanner.getSource(start, scanner.position()), "ab\nc", SourceSpan.of(10, 3, 13, 2), SourceSpan.of(11, 4, 20, 1)); scanner.next(); - assertSourceLines(scanner.getSource(start, scanner.position()), + assertSourceLines( + scanner.getSource(start, scanner.position()), "ab\ncd", SourceSpan.of(10, 3, 13, 2), SourceSpan.of(11, 4, 20, 2)); scanner.next(); - assertSourceLines(scanner.getSource(start, scanner.position()), + assertSourceLines( + scanner.getSource(start, scanner.position()), "ab\ncde", SourceSpan.of(10, 3, 13, 2), SourceSpan.of(11, 4, 20, 3)); - assertSourceLines(scanner.getSource(afterA, scanner.position()), + assertSourceLines( + scanner.getSource(afterA, scanner.position()), "b\ncde", SourceSpan.of(10, 4, 14, 1), SourceSpan.of(11, 4, 20, 3)); - assertSourceLines(scanner.getSource(afterB, scanner.position()), + assertSourceLines( + scanner.getSource(afterB, scanner.position()), "\ncde", SourceSpan.of(11, 4, 20, 3)); } - private void assertSourceLines(SourceLines sourceLines, String expectedContent, SourceSpan... expectedSourceSpans) { + private void assertSourceLines( + SourceLines sourceLines, String expectedContent, SourceSpan... expectedSourceSpans) { assertThat(sourceLines.getContent()).isEqualTo(expectedContent); assertThat(sourceLines.getSourceSpans()).isEqualTo(List.of(expectedSourceSpans)); } @Test void nextString() { - Scanner scanner = Scanner.of(SourceLines.of(List.of( - SourceLine.of("hey ya", null), - SourceLine.of("hi", null)))); + Scanner scanner = + Scanner.of( + SourceLines.of( + List.of(SourceLine.of("hey ya", null), SourceLine.of("hi", null)))); assertThat(scanner.next("hoy")).isFalse(); assertThat(scanner.next("hey")).isTrue(); assertThat(scanner.next(' ')).isTrue(); diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index 6a468a08e..c51fb895f 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -1,15 +1,14 @@ package org.commonmark.renderer.markdown; +import static org.assertj.core.api.Assertions.assertThat; +import static org.commonmark.testutil.Asserts.assertRendering; + +import java.util.Set; import org.commonmark.node.*; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.junit.jupiter.api.Test; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.commonmark.testutil.Asserts.assertRendering; - public class MarkdownRendererTest { // Leaf blocks @@ -120,12 +119,14 @@ public void testBulletListItems() { // Tight list where the second item contains a loose list assertRoundTrip("- Foo\n - Bar\n \n - Baz\n"); - // List item indent. This is a tricky one, but here the amount of space between the list marker and "one" - // determines whether "two" is part of the list item or an indented code block. - // In this case, it's an indented code block because it's not indented enough to be part of the list item. - // If the renderer would just use "- one", then "two" would change from being an indented code block to being - // a paragraph in the list item! So it is important for the renderer to preserve the content indent of the list - // item. + // List item indent. This is a tricky one, but here the amount of space between the list + // marker and "one" determines whether "two" is part of the list item or an indented code + // block. + // In this case, it's an indented code block because it's not indented enough to be part of + // the list item. + // If the renderer would just use "- one", then "two" would change from being an indented + // code block to being a paragraph in the list item! So it is important for the renderer to + // preserve the content indent of the list item. assertRoundTrip(" - one\n\n two\n"); // Empty list @@ -192,8 +193,8 @@ public void testTabs() { @Test public void testEscaping() { - // These are a bit tricky. We always escape some characters, even though they only need escaping if they would - // otherwise result in a different parse result (e.g. a link): + // These are a bit tricky. We always escape some characters, even though they only need + // escaping if they would otherwise result in a different parse result (e.g. a link): assertRoundTrip("\\[a\\](/uri)\n"); assertRoundTrip("\\`abc\\`\n"); @@ -250,7 +251,8 @@ public void testEmphasis() { // Not emphasis (needs * inside words) assertRoundTrip("foo\\_bar\\_\n"); - // Even when rendering a manually constructed tree, the emphasis delimiter needs to be chosen correctly. + // Even when rendering a manually constructed tree, the emphasis delimiter needs to be + // chosen correctly. Document doc = new Document(); Paragraph p = new Paragraph(); doc.appendChild(p); @@ -312,29 +314,31 @@ public void testSoftLineBreaks() { @Test public void overrideNodeRender() { - var nodeRendererFactory = new MarkdownNodeRendererFactory() { - @Override - public NodeRenderer create(MarkdownNodeRendererContext context) { - return new NodeRenderer() { + var nodeRendererFactory = + new MarkdownNodeRendererFactory() { @Override - public Set> getNodeTypes() { - return Set.of(Heading.class); + public NodeRenderer create(MarkdownNodeRendererContext context) { + return new NodeRenderer() { + @Override + public Set> getNodeTypes() { + return Set.of(Heading.class); + } + + @Override + public void render(Node node) { + context.getWriter().raw("# Custom heading"); + } + }; } @Override - public void render(Node node) { - context.getWriter().raw("# Custom heading"); + public Set getSpecialCharacters() { + return Set.of(); } }; - } - - @Override - public Set getSpecialCharacters() { - return Set.of(); - } - }; - MarkdownRenderer renderer = MarkdownRenderer.builder().nodeRendererFactory(nodeRendererFactory).build(); + MarkdownRenderer renderer = + MarkdownRenderer.builder().nodeRendererFactory(nodeRendererFactory).build(); String rendered = renderer.render(parse("# Hello")); assertThat(rendered).isEqualTo("# Custom heading\n"); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 3b88df55d..e2584eeb8 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -1,5 +1,11 @@ package org.commonmark.renderer.markdown; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; @@ -8,27 +14,22 @@ import org.commonmark.testutil.example.ExampleReader; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - /** * Tests Markdown rendering using the examples in the spec like this: + * *

      - *
    1. Parses the source to an AST and then renders it back to Markdown
    2. - *
    3. Parses that to an AST and then renders it to HTML
    4. - *
    5. Compares that HTML to the expected HTML of the example: - * If it's the same, then the expected elements were preserved in the Markdown rendering
    6. + *
    7. Parses the source to an AST and then renders it back to Markdown + *
    8. Parses that to an AST and then renders it to HTML + *
    9. Compares that HTML to the expected HTML of the example: If it's the same, then the expected + * elements were preserved in the Markdown rendering *
    */ public class SpecMarkdownRendererTest { public static final MarkdownRenderer MARKDOWN_RENDERER = MarkdownRenderer.builder().build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. - public static final HtmlRenderer HTML_RENDERER = HtmlRenderer.builder().percentEncodeUrls(true).build(); + public static final HtmlRenderer HTML_RENDERER = + HtmlRenderer.builder().percentEncodeUrls(true).build(); @Test public void testCoverage() { diff --git a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java index edb6936f4..fbc7f86bb 100644 --- a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java @@ -1,21 +1,22 @@ package org.commonmark.test; +import static org.assertj.core.api.Assertions.assertThat; + import org.commonmark.node.*; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class AbstractVisitorTest { @Test public void replacingNodeInVisitorShouldNotDestroyVisitOrder() { - Visitor visitor = new AbstractVisitor() { - @Override - public void visit(Text text) { - text.insertAfter(new Code(text.getLiteral())); - text.unlink(); - } - }; + Visitor visitor = + new AbstractVisitor() { + @Override + public void visit(Text text) { + text.insertAfter(new Code(text.getLiteral())); + text.unlink(); + } + }; Paragraph paragraph = new Paragraph(); paragraph.appendChild(new Text("foo")); diff --git a/commonmark/src/test/java/org/commonmark/test/BlockParserFactoryTest.java b/commonmark/src/test/java/org/commonmark/test/BlockParserFactoryTest.java index b733d7970..e88423aed 100644 --- a/commonmark/src/test/java/org/commonmark/test/BlockParserFactoryTest.java +++ b/commonmark/src/test/java/org/commonmark/test/BlockParserFactoryTest.java @@ -1,5 +1,8 @@ package org.commonmark.test; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; import org.commonmark.node.*; import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.InlineParser; @@ -8,15 +11,12 @@ import org.commonmark.parser.block.*; import org.junit.jupiter.api.Test; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - public class BlockParserFactoryTest { @Test public void customBlockParserFactory() { - var parser = Parser.builder().customBlockParserFactory(new DashBlockParser.Factory()).build(); + var parser = + Parser.builder().customBlockParserFactory(new DashBlockParser.Factory()).build(); // The dashes would normally be a ThematicBreak var doc = parser.parse("hey\n\n---\n"); @@ -28,10 +28,11 @@ public void customBlockParserFactory() { @Test public void replaceActiveBlockParser() { - var parser = Parser.builder() - .customBlockParserFactory(new StarHeadingBlockParser.Factory()) - .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) - .build(); + var parser = + Parser.builder() + .customBlockParserFactory(new StarHeadingBlockParser.Factory()) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); var doc = parser.parse("a\nbc\n***\n"); @@ -46,16 +47,17 @@ public void replaceActiveBlockParser() { assertThat(((Text) bc).getLiteral()).isEqualTo("bc"); assertThat(bc.getNext()).isNull(); - assertThat(heading.getSourceSpans()).isEqualTo(List.of( - SourceSpan.of(0, 0, 0, 1), - SourceSpan.of(1, 0, 2, 2), - SourceSpan.of(2, 0, 5, 3))); + assertThat(heading.getSourceSpans()) + .isEqualTo( + List.of( + SourceSpan.of(0, 0, 0, 1), + SourceSpan.of(1, 0, 2, 2), + SourceSpan.of(2, 0, 5, 3))); assertThat(a.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 1))); assertThat(bc.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(1, 0, 2, 2))); } - private static class DashBlock extends CustomBlock { - } + private static class DashBlock extends CustomBlock {} private static class DashBlockParser extends AbstractBlockParser { @@ -83,8 +85,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } } - private static class StarHeading extends CustomBlock { - } + private static class StarHeading extends CustomBlock {} private static class StarHeadingBlockParser extends AbstractBlockParser { diff --git a/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java b/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java index 3f2f0d611..06007e4bd 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java @@ -1,38 +1,36 @@ package org.commonmark.test; -import org.commonmark.node.*; -import org.commonmark.parser.Parser; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; +import org.commonmark.node.*; +import org.commonmark.parser.Parser; +import org.junit.jupiter.api.Test; public class DelimitedTest { @Test public void emphasisDelimiters() { - String input = "* *emphasis* \n" - + "* **strong** \n" - + "* _important_ \n" - + "* __CRITICAL__ \n"; + String input = + "* *emphasis* \n" + "* **strong** \n" + "* _important_ \n" + "* __CRITICAL__ \n"; Parser parser = Parser.builder().build(); Node document = parser.parse(input); final List list = new ArrayList<>(); - Visitor visitor = new AbstractVisitor() { - @Override - public void visit(Emphasis node) { - list.add(node); - } - - @Override - public void visit(StrongEmphasis node) { - list.add(node); - } - }; + Visitor visitor = + new AbstractVisitor() { + @Override + public void visit(Emphasis node) { + list.add(node); + } + + @Override + public void visit(StrongEmphasis node) { + list.add(node); + } + }; document.accept(visitor); assertThat(list).hasSize(4); diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index e4920120d..a185c293c 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -1,5 +1,10 @@ package org.commonmark.test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Locale; +import java.util.Set; import org.commonmark.node.CustomNode; import org.commonmark.node.Node; import org.commonmark.node.Text; @@ -13,23 +18,20 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import java.util.Locale; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - public class DelimiterProcessorTest extends RenderingTestCase { - private static final Parser PARSER = Parser.builder().customDelimiterProcessor(new AsymmetricDelimiterProcessor()).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().nodeRendererFactory(new UpperCaseNodeRendererFactory()).build(); + private static final Parser PARSER = + Parser.builder().customDelimiterProcessor(new AsymmetricDelimiterProcessor()).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().nodeRendererFactory(new UpperCaseNodeRendererFactory()).build(); @Test public void delimiterProcessorWithInvalidDelimiterUse() { - Parser parser = Parser.builder() - .customDelimiterProcessor(new CustomDelimiterProcessor(':', 0)) - .customDelimiterProcessor(new CustomDelimiterProcessor(';', -1)) - .build(); + Parser parser = + Parser.builder() + .customDelimiterProcessor(new CustomDelimiterProcessor(':', 0)) + .customDelimiterProcessor(new CustomDelimiterProcessor(';', -1)) + .build(); assertThat(RENDERER.render(parser.parse(":test:"))).isEqualTo("

    :test:

    \n"); assertThat(RENDERER.render(parser.parse(";test;"))).isEqualTo("

    ;test;

    \n"); } @@ -51,21 +53,26 @@ public void asymmetricDelimiter() { @Test public void multipleDelimitersWithDifferentLengths() { - Parser parser = Parser.builder() - .customDelimiterProcessor(new OneDelimiterProcessor()) - .customDelimiterProcessor(new TwoDelimiterProcessor()) - .build(); - assertThat(RENDERER.render(parser.parse("+one+ ++two++"))).isEqualTo("

    (1)one(/1) (2)two(/2)

    \n"); - assertThat(RENDERER.render(parser.parse("+++both+++"))).isEqualTo("

    (1)(2)both(/2)(/1)

    \n"); + Parser parser = + Parser.builder() + .customDelimiterProcessor(new OneDelimiterProcessor()) + .customDelimiterProcessor(new TwoDelimiterProcessor()) + .build(); + assertThat(RENDERER.render(parser.parse("+one+ ++two++"))) + .isEqualTo("

    (1)one(/1) (2)two(/2)

    \n"); + assertThat(RENDERER.render(parser.parse("+++both+++"))) + .isEqualTo("

    (1)(2)both(/2)(/1)

    \n"); } @Test public void multipleDelimitersWithSameLengthConflict() { - assertThatThrownBy(() -> - Parser.builder() - .customDelimiterProcessor(new OneDelimiterProcessor()) - .customDelimiterProcessor(new OneDelimiterProcessor()) - .build()).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy( + () -> + Parser.builder() + .customDelimiterProcessor(new OneDelimiterProcessor()) + .customDelimiterProcessor(new OneDelimiterProcessor()) + .build()) + .isInstanceOf(IllegalArgumentException.class); } @Override @@ -139,8 +146,7 @@ public int process(DelimiterRun openingRun, DelimiterRun closingRun) { } } - private static class UpperCaseNode extends CustomNode { - } + private static class UpperCaseNode extends CustomNode {} private static class UpperCaseNodeRendererFactory implements HtmlNodeRendererFactory { @@ -166,7 +172,9 @@ public Set> getNodeTypes() { @Override public void render(Node node) { UpperCaseNode upperCaseNode = (UpperCaseNode) node; - for (Node child = upperCaseNode.getFirstChild(); child != null; child = child.getNext()) { + for (Node child = upperCaseNode.getFirstChild(); + child != null; + child = child.getNext()) { if (child instanceof Text) { Text text = (Text) child; text.setLiteral(text.getLiteral().toUpperCase(Locale.ENGLISH)); diff --git a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java index 443b0fa51..a07519756 100644 --- a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java @@ -1,5 +1,7 @@ package org.commonmark.test; +import static org.assertj.core.api.Assertions.assertThat; + import org.commonmark.node.FencedCodeBlock; import org.commonmark.node.Node; import org.commonmark.parser.Parser; @@ -7,8 +9,6 @@ import org.commonmark.testutil.RenderingTestCase; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class FencedCodeBlockParserTest extends RenderingTestCase { private static final Parser PARSER = Parser.builder().build(); @@ -24,36 +24,35 @@ public void backtickInfo() { @Test public void backtickInfoDoesntAllowBacktick() { - assertRendering("```info ` test\ncode\n```", + assertRendering( + "```info ` test\ncode\n```", "

    ```info ` test\ncode

    \n
    \n"); } @Test public void backtickAndTildeCantBeMixed() { - assertRendering("``~`\ncode\n``~`", - "

    ~` code ~`

    \n"); + assertRendering("``~`\ncode\n``~`", "

    ~` code ~`

    \n"); } @Test public void closingCanHaveSpacesAfter() { - assertRendering("```\ncode\n``` ", - "
    code\n
    \n"); + assertRendering("```\ncode\n``` ", "
    code\n
    \n"); } @Test public void closingCanNotHaveNonSpaces() { - assertRendering("```\ncode\n``` a", - "
    code\n``` a\n
    \n"); + assertRendering("```\ncode\n``` a", "
    code\n``` a\n
    \n"); } @Test public void issue151() { - assertRendering("```\nthis code\n\nshould not have BRs or paragraphs in it\nok\n```", - "
    this code\n" +
    -                        "\n" +
    -                        "should not have BRs or paragraphs in it\n" +
    -                        "ok\n" +
    -                        "
    \n"); + assertRendering( + "```\nthis code\n\nshould not have BRs or paragraphs in it\nok\n```", + "
    this code\n"
    +                        + "\n"
    +                        + "should not have BRs or paragraphs in it\n"
    +                        + "ok\n"
    +                        + "
    \n"); } @Override diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 92751147a..cd0811c23 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -1,45 +1,62 @@ package org.commonmark.test; -import org.commonmark.node.*; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.html.*; -import org.commonmark.testutil.TestResources; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.Future; - -import static org.assertj.core.api.Assertions.assertThat; +import org.commonmark.node.*; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.html.*; +import org.commonmark.testutil.TestResources; +import org.junit.jupiter.api.Test; public class HtmlRendererTest { @Test public void htmlAllowingShouldNotEscapeInlineHtml() { - String rendered = htmlAllowingRenderer().render(parse("paragraph with inline & html")); - assertThat(rendered).isEqualTo("

    paragraph with inline & html

    \n"); + String rendered = + htmlAllowingRenderer() + .render( + parse( + "paragraph with inline & html")); + assertThat(rendered) + .isEqualTo( + "

    paragraph with inline & html

    \n"); } @Test public void htmlAllowingShouldNotEscapeBlockHtml() { - String rendered = htmlAllowingRenderer().render(parse("
    block &
    ")); + String rendered = + htmlAllowingRenderer() + .render(parse("
    block &
    ")); assertThat(rendered).isEqualTo("
    block &
    \n"); } @Test public void htmlEscapingShouldEscapeInlineHtml() { - String rendered = htmlEscapingRenderer().render(parse("paragraph with inline & html")); + String rendered = + htmlEscapingRenderer() + .render( + parse( + "paragraph with inline & html")); // Note that & is not escaped, as it's a normal text node, not part of the inline HTML. - assertThat(rendered).isEqualTo("

    paragraph with <span id='foo' class="bar">inline & html</span>

    \n"); + assertThat(rendered) + .isEqualTo( + "

    paragraph with <span id='foo' class="bar">inline & html</span>

    \n"); } @Test public void htmlEscapingShouldEscapeHtmlBlocks() { - String rendered = htmlEscapingRenderer().render(parse("
    block &
    ")); - assertThat(rendered).isEqualTo("

    <div id='foo' class="bar">block &amp;</div>

    \n"); + String rendered = + htmlEscapingRenderer() + .render(parse("
    block &
    ")); + assertThat(rendered) + .isEqualTo( + "

    <div id='foo' class="bar">block &amp;</div>

    \n"); } @Test @@ -50,9 +67,12 @@ public void textEscaping() { @Test public void characterReferencesWithoutSemicolonsShouldNotBeParsedShouldBeEscaped() { - String input = "[example](javascript:alert('XSS'))"; + String input = + "[example](javascript:alert('XSS'))"; String rendered = defaultRenderer().render(parse(input)); - assertThat(rendered).isEqualTo("

    example

    \n"); + assertThat(rendered) + .isEqualTo( + "

    example

    \n"); } @Test @@ -61,7 +81,8 @@ public void attributeEscaping() { Link link = new Link(); link.setDestination(":"); paragraph.appendChild(link); - assertThat(defaultRenderer().render(paragraph)).isEqualTo("

    \n"); + assertThat(defaultRenderer().render(paragraph)) + .isEqualTo("

    \n"); } @Test @@ -70,7 +91,8 @@ public void rawUrlsShouldNotFilterDangerousProtocols() { Link link = new Link(); link.setDestination("javascript:alert(5);"); paragraph.appendChild(link); - assertThat(rawUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); + assertThat(rawUrlsRenderer().render(paragraph)) + .isEqualTo("

    \n"); } @Test @@ -79,13 +101,15 @@ public void sanitizedUrlsShouldSetRelNoFollow() { Link link = new Link(); link.setDestination("/exampleUrl"); paragraph.appendChild(link); - assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); + assertThat(sanitizeUrlsRenderer().render(paragraph)) + .isEqualTo("

    \n"); paragraph = new Paragraph(); link = new Link(); link.setDestination("https://google.com"); paragraph.appendChild(link); - assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); + assertThat(sanitizeUrlsRenderer().render(paragraph)) + .isEqualTo("

    \n"); } @Test @@ -94,26 +118,31 @@ public void sanitizedUrlsShouldAllowSafeProtocols() { Link link = new Link(); link.setDestination("http://google.com"); paragraph.appendChild(link); - assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); + assertThat(sanitizeUrlsRenderer().render(paragraph)) + .isEqualTo("

    \n"); paragraph = new Paragraph(); link = new Link(); link.setDestination("https://google.com"); paragraph.appendChild(link); - assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); + assertThat(sanitizeUrlsRenderer().render(paragraph)) + .isEqualTo("

    \n"); paragraph = new Paragraph(); link = new Link(); link.setDestination("mailto:foo@bar.example.com"); paragraph.appendChild(link); - assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); + assertThat(sanitizeUrlsRenderer().render(paragraph)) + .isEqualTo("

    \n"); - String image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAAAAQSURBVBhXY/iPBVBf8P9/AG8TY51nJdgkAAAAAElFTkSuQmCC"; + String image = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAAAAQSURBVBhXY/iPBVBf8P9/AG8TY51nJdgkAAAAAElFTkSuQmCC"; paragraph = new Paragraph(); link = new Link(); link.setDestination(image); paragraph.appendChild(link); - assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); + assertThat(sanitizeUrlsRenderer().render(paragraph)) + .isEqualTo("

    \n"); } @Test @@ -122,74 +151,103 @@ public void sanitizedUrlsShouldFilterDangerousProtocols() { Link link = new Link(); link.setDestination("javascript:alert(5);"); paragraph.appendChild(link); - assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); + assertThat(sanitizeUrlsRenderer().render(paragraph)) + .isEqualTo("

    \n"); paragraph = new Paragraph(); link = new Link(); link.setDestination("ftp://google.com"); paragraph.appendChild(link); - assertThat(sanitizeUrlsRenderer().render(paragraph)).isEqualTo("

    \n"); + assertThat(sanitizeUrlsRenderer().render(paragraph)) + .isEqualTo("

    \n"); } @Test public void percentEncodeUrlDisabled() { - assertThat(defaultRenderer().render(parse("[a](foo&bar)"))).isEqualTo("

    a

    \n"); - assertThat(defaultRenderer().render(parse("[a](ä)"))).isEqualTo("

    a

    \n"); - assertThat(defaultRenderer().render(parse("[a](foo%20bar)"))).isEqualTo("

    a

    \n"); + assertThat(defaultRenderer().render(parse("[a](foo&bar)"))) + .isEqualTo("

    a

    \n"); + assertThat(defaultRenderer().render(parse("[a](ä)"))) + .isEqualTo("

    a

    \n"); + assertThat(defaultRenderer().render(parse("[a](foo%20bar)"))) + .isEqualTo("

    a

    \n"); } @Test public void percentEncodeUrl() { // Entities are escaped anyway - assertThat(percentEncodingRenderer().render(parse("[a](foo&bar)"))).isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](foo&bar)"))) + .isEqualTo("

    a

    \n"); // Existing encoding is preserved - assertThat(percentEncodingRenderer().render(parse("[a](foo%20bar)"))).isEqualTo("

    a

    \n"); - assertThat(percentEncodingRenderer().render(parse("[a](foo%61)"))).isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](foo%20bar)"))) + .isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](foo%61)"))) + .isEqualTo("

    a

    \n"); // Invalid encoding is escaped - assertThat(percentEncodingRenderer().render(parse("[a](foo%)"))).isEqualTo("

    a

    \n"); - assertThat(percentEncodingRenderer().render(parse("[a](foo%a)"))).isEqualTo("

    a

    \n"); - assertThat(percentEncodingRenderer().render(parse("[a](foo%a_)"))).isEqualTo("

    a

    \n"); - assertThat(percentEncodingRenderer().render(parse("[a](foo%xx)"))).isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](foo%)"))) + .isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](foo%a)"))) + .isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](foo%a_)"))) + .isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](foo%xx)"))) + .isEqualTo("

    a

    \n"); // Reserved characters are preserved, except for '[' and ']' - assertThat(percentEncodingRenderer().render(parse("[a](!*'();:@&=+$,/?#[])"))).isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](!*'();:@&=+$,/?#[])"))) + .isEqualTo("

    a

    \n"); // Unreserved characters are preserved - assertThat(percentEncodingRenderer().render(parse("[a](ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~)"))).isEqualTo("

    a

    \n"); + assertThat( + percentEncodingRenderer() + .render( + parse( + "[a](ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~)"))) + .isEqualTo( + "

    a

    \n"); // Other characters are percent-encoded (LATIN SMALL LETTER A WITH DIAERESIS) - assertThat(percentEncodingRenderer().render(parse("[a](ä)"))).isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](ä)"))) + .isEqualTo("

    a

    \n"); // Other characters are percent-encoded (MUSICAL SYMBOL G CLEF, surrogate pair in UTF-16) - assertThat(percentEncodingRenderer().render(parse("[a](\uD834\uDD1E)"))).isEqualTo("

    a

    \n"); + assertThat(percentEncodingRenderer().render(parse("[a](\uD834\uDD1E)"))) + .isEqualTo("

    a

    \n"); } @Test public void attributeProviderForCodeBlock() { - AttributeProviderFactory custom = context -> (node, tagName, attributes) -> { - if (node instanceof FencedCodeBlock && tagName.equals("code")) { - FencedCodeBlock fencedCodeBlock = (FencedCodeBlock) node; - // Remove the default attribute for info - attributes.remove("class"); - // Put info in custom attribute instead - attributes.put("data-custom", fencedCodeBlock.getInfo()); - } else if (node instanceof FencedCodeBlock && tagName.equals("pre")) { - attributes.put("data-code-block", "fenced"); - } - }; + AttributeProviderFactory custom = + context -> + (node, tagName, attributes) -> { + if (node instanceof FencedCodeBlock && tagName.equals("code")) { + FencedCodeBlock fencedCodeBlock = (FencedCodeBlock) node; + // Remove the default attribute for info + attributes.remove("class"); + // Put info in custom attribute instead + attributes.put("data-custom", fencedCodeBlock.getInfo()); + } else if (node instanceof FencedCodeBlock && tagName.equals("pre")) { + attributes.put("data-code-block", "fenced"); + } + }; HtmlRenderer renderer = HtmlRenderer.builder().attributeProviderFactory(custom).build(); String rendered = renderer.render(parse("```info\ncontent\n```")); - assertThat(rendered).isEqualTo("
    content\n
    \n"); + assertThat(rendered) + .isEqualTo( + "
    content\n
    \n"); String rendered2 = renderer.render(parse("```evil\"\ncontent\n```")); - assertThat(rendered2).isEqualTo("
    content\n
    \n"); + assertThat(rendered2) + .isEqualTo( + "
    content\n
    \n"); } @Test public void attributeProviderForImage() { - AttributeProviderFactory custom = context -> (node, tagName, attributes) -> { - if (node instanceof Image) { - attributes.remove("alt"); - attributes.put("test", "hey"); - } - }; + AttributeProviderFactory custom = + context -> + (node, tagName, attributes) -> { + if (node instanceof Image) { + attributes.remove("alt"); + attributes.put("test", "hey"); + } + }; HtmlRenderer renderer = HtmlRenderer.builder().attributeProviderFactory(custom).build(); String rendered = renderer.render(parse("![foo](/url)\n")); @@ -198,15 +256,18 @@ public void attributeProviderForImage() { @Test public void attributeProviderFactoryNewInstanceForEachRender() { - AttributeProviderFactory factory = context -> new AttributeProvider() { - int i = 0; - - @Override - public void setAttributes(Node node, String tagName, Map attributes) { - attributes.put("key", "" + i); - i++; - } - }; + AttributeProviderFactory factory = + context -> + new AttributeProvider() { + int i = 0; + + @Override + public void setAttributes( + Node node, String tagName, Map attributes) { + attributes.put("key", "" + i); + i++; + } + }; HtmlRenderer renderer = HtmlRenderer.builder().attributeProviderFactory(factory).build(); String rendered = renderer.render(parse("text node")); @@ -216,51 +277,60 @@ public void setAttributes(Node node, String tagName, Map attribu @Test public void overrideNodeRender() { - HtmlNodeRendererFactory nodeRendererFactory = context -> new NodeRenderer() { - @Override - public Set> getNodeTypes() { - return Set.of(Link.class); - } - - @Override - public void render(Node node) { - context.getWriter().text("test"); - } - }; - - HtmlRenderer renderer = HtmlRenderer.builder().nodeRendererFactory(nodeRendererFactory).build(); + HtmlNodeRendererFactory nodeRendererFactory = + context -> + new NodeRenderer() { + @Override + public Set> getNodeTypes() { + return Set.of(Link.class); + } + + @Override + public void render(Node node) { + context.getWriter().text("test"); + } + }; + + HtmlRenderer renderer = + HtmlRenderer.builder().nodeRendererFactory(nodeRendererFactory).build(); String rendered = renderer.render(parse("foo [bar](/url)")); assertThat(rendered).isEqualTo("

    foo test

    \n"); } @Test public void orderedListStartZero() { - assertThat(defaultRenderer().render(parse("0. Test\n"))).isEqualTo("
      \n
    1. Test
    2. \n
    \n"); + assertThat(defaultRenderer().render(parse("0. Test\n"))) + .isEqualTo("
      \n
    1. Test
    2. \n
    \n"); } @Test public void imageAltTextWithSoftLineBreak() { - assertThat(defaultRenderer().render(parse("![foo\nbar](/url)\n"))).isEqualTo("

    \"foo\nbar\"

    \n"); + assertThat(defaultRenderer().render(parse("![foo\nbar](/url)\n"))) + .isEqualTo("

    \"foo\nbar\"

    \n"); } @Test public void imageAltTextWithHardLineBreak() { - assertThat(defaultRenderer().render(parse("![foo \nbar](/url)\n"))).isEqualTo("

    \"foo\nbar\"

    \n"); + assertThat(defaultRenderer().render(parse("![foo \nbar](/url)\n"))) + .isEqualTo("

    \"foo\nbar\"

    \n"); } @Test public void imageAltTextWithEntities() { - assertThat(defaultRenderer().render(parse("![foo ä](/url)\n"))).isEqualTo("

    \"foo

    \n"); + assertThat(defaultRenderer().render(parse("![foo ä](/url)\n"))) + .isEqualTo("

    \"foo

    \n"); } @Test public void imageAltTextWithInlines() { - assertThat(defaultRenderer().render(parse("![_foo_ **bar** [link](/url)](/url)\n"))).isEqualTo("

    \"foo

    \n"); + assertThat(defaultRenderer().render(parse("![_foo_ **bar** [link](/url)](/url)\n"))) + .isEqualTo("

    \"foo

    \n"); } @Test public void imageAltTextWithCode() { - assertThat(defaultRenderer().render(parse("![`foo` bar](/url)\n"))).isEqualTo("

    \"foo

    \n"); + assertThat(defaultRenderer().render(parse("![`foo` bar](/url)\n"))) + .isEqualTo("

    \"foo

    \n"); } @Test @@ -277,7 +347,8 @@ public void canRenderContentsOfSingleParagraph() { document.appendChild(current); } - assertThat(defaultRenderer().render(document)).isEqualTo("Here I have a test link"); + assertThat(defaultRenderer().render(document)) + .isEqualTo("Here I have a test link"); } @Test @@ -318,7 +389,10 @@ private static HtmlRenderer htmlAllowingRenderer() { } private static HtmlRenderer sanitizeUrlsRenderer() { - return HtmlRenderer.builder().sanitizeUrls(true).urlSanitizer(new DefaultUrlSanitizer()).build(); + return HtmlRenderer.builder() + .sanitizeUrls(true) + .urlSanitizer(new DefaultUrlSanitizer()) + .build(); } private static HtmlRenderer rawUrlsRenderer() { diff --git a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java index c05cac2d2..6d810abdd 100644 --- a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java +++ b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java @@ -1,23 +1,22 @@ package org.commonmark.test; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; import org.commonmark.internal.InlineParserImpl; -import org.commonmark.parser.beta.LinkProcessor; -import org.commonmark.parser.beta.InlineContentParserFactory; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.InlineParserFactory; import org.commonmark.parser.Parser; +import org.commonmark.parser.beta.InlineContentParserFactory; +import org.commonmark.parser.beta.LinkProcessor; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.renderer.html.HtmlRenderer; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - public class InlineParserContextTest { @Test @@ -42,38 +41,40 @@ static class CapturingInlineParserFactory implements InlineParserFactory { @Override public InlineParser create(final InlineParserContext inlineParserContext) { - InlineParserContext wrappedContext = new InlineParserContext() { - @Override - public List getCustomInlineContentParserFactories() { - return inlineParserContext.getCustomInlineContentParserFactories(); - } - - @Override - public List getCustomDelimiterProcessors() { - return inlineParserContext.getCustomDelimiterProcessors(); - } - - @Override - public List getCustomLinkProcessors() { - return inlineParserContext.getCustomLinkProcessors(); - } - - @Override - public Set getCustomLinkMarkers() { - return inlineParserContext.getCustomLinkMarkers(); - } - - @Override - public LinkReferenceDefinition getLinkReferenceDefinition(String label) { - return getDefinition(LinkReferenceDefinition.class, label); - } - - @Override - public D getDefinition(Class type, String label) { - lookups.add(label); - return inlineParserContext.getDefinition(type, label); - } - }; + InlineParserContext wrappedContext = + new InlineParserContext() { + @Override + public List + getCustomInlineContentParserFactories() { + return inlineParserContext.getCustomInlineContentParserFactories(); + } + + @Override + public List getCustomDelimiterProcessors() { + return inlineParserContext.getCustomDelimiterProcessors(); + } + + @Override + public List getCustomLinkProcessors() { + return inlineParserContext.getCustomLinkProcessors(); + } + + @Override + public Set getCustomLinkMarkers() { + return inlineParserContext.getCustomLinkMarkers(); + } + + @Override + public LinkReferenceDefinition getLinkReferenceDefinition(String label) { + return getDefinition(LinkReferenceDefinition.class, label); + } + + @Override + public D getDefinition(Class type, String label) { + lookups.add(label); + return inlineParserContext.getDefinition(type, label); + } + }; return new InlineParserImpl(wrappedContext); } diff --git a/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java b/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java index 8410ff028..f3ca037ca 100644 --- a/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java +++ b/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java @@ -1,13 +1,12 @@ package org.commonmark.test; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; import org.commonmark.node.*; import org.commonmark.parser.Parser; import org.junit.jupiter.api.Test; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - public class LinkReferenceDefinitionNodeTest { @Test @@ -47,14 +46,16 @@ public void testMultipleDefinitions() { @Test public void testMultipleDefinitionsWithSameLabel() { - Node document = parse("This is a paragraph with a [foo] link.\n\n[foo]: /url1\n[foo]: /url2"); + Node document = + parse("This is a paragraph with a [foo] link.\n\n[foo]: /url1\n[foo]: /url2"); List nodes = Nodes.getChildren(document); assertThat(nodes).hasSize(3); assertThat(nodes.get(0)).isInstanceOf(Paragraph.class); LinkReferenceDefinition def1 = assertDef(nodes.get(1), "foo"); assertThat(def1.getDestination()).isEqualTo("/url1"); - // When there's multiple definitions with the same label, the first one "wins", as in reference links will use + // When there's multiple definitions with the same label, the first one "wins", as in + // reference links will use // that. But we still want to preserve the original definitions in the document. LinkReferenceDefinition def2 = assertDef(nodes.get(2), "foo"); assertThat(def2.getDestination()).isEqualTo("/url2"); diff --git a/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java b/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java index 02ac3abff..68241cc32 100644 --- a/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ListBlockParserTest.java @@ -1,12 +1,12 @@ package org.commonmark.test; +import static org.assertj.core.api.Assertions.assertThat; + import org.commonmark.node.ListItem; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - public class ListBlockParserTest { private static final Parser PARSER = Parser.builder().build(); @@ -56,7 +56,8 @@ public void testOrderedListIndents() { assertListItemIndents("1.\tfoo", 0, 4); } - private void assertListItemIndents(String input, int expectedMarkerIndent, int expectedContentIndent) { + private void assertListItemIndents( + String input, int expectedMarkerIndent, int expectedContentIndent) { Node doc = PARSER.parse(input); ListItem listItem = Nodes.find(doc, ListItem.class); assertThat((int) listItem.getMarkerIndent()).isEqualTo(expectedMarkerIndent); diff --git a/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java b/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java index c6bda31ed..e606d467e 100644 --- a/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java @@ -6,188 +6,150 @@ public class ListTightLooseTest extends CoreRenderingTestCase { @Test public void tight() { - assertRendering("- foo\n" + - "- bar\n" + - "+ baz\n", - "
      \n" + - "
    • foo
    • \n" + - "
    • bar
    • \n" + - "
    \n" + - "
      \n" + - "
    • baz
    • \n" + - "
    \n"); + assertRendering( + "- foo\n" + "- bar\n" + "+ baz\n", + "
      \n" + + "
    • foo
    • \n" + + "
    • bar
    • \n" + + "
    \n" + + "
      \n" + + "
    • baz
    • \n" + + "
    \n"); } @Test public void loose() { - assertRendering("- foo\n" + - "\n" + - "- bar\n" + - "\n" + - "\n" + - "- baz\n", - "
      \n" + - "
    • \n" + - "

      foo

      \n" + - "
    • \n" + - "
    • \n" + - "

      bar

      \n" + - "
    • \n" + - "
    • \n" + - "

      baz

      \n" + - "
    • \n" + - "
    \n"); + assertRendering( + "- foo\n" + "\n" + "- bar\n" + "\n" + "\n" + "- baz\n", + "
      \n" + + "
    • \n" + + "

      foo

      \n" + + "
    • \n" + + "
    • \n" + + "

      bar

      \n" + + "
    • \n" + + "
    • \n" + + "

      baz

      \n" + + "
    • \n" + + "
    \n"); } @Test public void looseNested() { - assertRendering("- foo\n" + - " - bar\n" + - "\n" + - "\n" + - " baz", - "
      \n" + - "
    • foo\n" + - "
        \n" + - "
      • \n" + - "

        bar

        \n" + - "

        baz

        \n" + - "
      • \n" + - "
      \n" + - "
    • \n" + - "
    \n"); + assertRendering( + "- foo\n" + " - bar\n" + "\n" + "\n" + " baz", + "
      \n" + + "
    • foo\n" + + "
        \n" + + "
      • \n" + + "

        bar

        \n" + + "

        baz

        \n" + + "
      • \n" + + "
      \n" + + "
    • \n" + + "
    \n"); } @Test public void looseNested2() { - assertRendering("- a\n" + - " - b\n" + - "\n" + - " c\n" + - "- d\n", - "
      \n" + - "
    • a\n" + - "
        \n" + - "
      • \n" + - "

        b

        \n" + - "

        c

        \n" + - "
      • \n" + - "
      \n" + - "
    • \n" + - "
    • d
    • \n" + - "
    \n"); + assertRendering( + "- a\n" + " - b\n" + "\n" + " c\n" + "- d\n", + "
      \n" + + "
    • a\n" + + "
        \n" + + "
      • \n" + + "

        b

        \n" + + "

        c

        \n" + + "
      • \n" + + "
      \n" + + "
    • \n" + + "
    • d
    • \n" + + "
    \n"); } @Test public void looseOuter() { - assertRendering("- foo\n" + - " - bar\n" + - "\n" + - "\n" + - " baz", - "
      \n" + - "
    • \n" + - "

      foo

      \n" + - "
        \n" + - "
      • bar
      • \n" + - "
      \n" + - "

      baz

      \n" + - "
    • \n" + - "
    \n"); + assertRendering( + "- foo\n" + " - bar\n" + "\n" + "\n" + " baz", + "
      \n" + + "
    • \n" + + "

      foo

      \n" + + "
        \n" + + "
      • bar
      • \n" + + "
      \n" + + "

      baz

      \n" + + "
    • \n" + + "
    \n"); } @Test public void looseListItem() { - assertRendering("- one\n" + - "\n" + - " two\n", - "
      \n" + - "
    • \n" + - "

      one

      \n" + - "

      two

      \n" + - "
    • \n" + - "
    \n"); + assertRendering( + "- one\n" + "\n" + " two\n", + "
      \n" + "
    • \n" + "

      one

      \n" + "

      two

      \n" + "
    • \n" + "
    \n"); } @Test public void tightWithBlankLineAfter() { - assertRendering("- foo\n" + - "- bar\n" + - "\n", - "
      \n" + - "
    • foo
    • \n" + - "
    • bar
    • \n" + - "
    \n"); + assertRendering( + "- foo\n" + "- bar\n" + "\n", + "
      \n" + "
    • foo
    • \n" + "
    • bar
    • \n" + "
    \n"); } @Test public void tightListWithCodeBlock() { - assertRendering("- a\n" + - "- ```\n" + - " b\n" + - "\n" + - "\n" + - " ```\n" + - "- c\n", - "
      \n" + - "
    • a
    • \n" + - "
    • \n" + - "
      b\n" +
      -                        "\n" +
      -                        "\n" +
      -                        "
      \n" + - "
    • \n" + - "
    • c
    • \n" + - "
    \n"); + assertRendering( + "- a\n" + "- ```\n" + " b\n" + "\n" + "\n" + " ```\n" + "- c\n", + "
      \n" + + "
    • a
    • \n" + + "
    • \n" + + "
      b\n"
      +                        + "\n"
      +                        + "\n"
      +                        + "
      \n" + + "
    • \n" + + "
    • c
    • \n" + + "
    \n"); } @Test public void tightListWithCodeBlock2() { - assertRendering("* foo\n" + - " ```\n" + - " bar\n" + - "\n" + - " ```\n" + - " baz\n", - "
      \n" + - "
    • foo\n" + - "
      bar\n" +
      -                        "\n" +
      -                        "
      \n" + - "baz
    • \n" + - "
    \n"); + assertRendering( + "* foo\n" + " ```\n" + " bar\n" + "\n" + " ```\n" + " baz\n", + "
      \n" + + "
    • foo\n" + + "
      bar\n"
      +                        + "\n"
      +                        + "
      \n" + + "baz
    • \n" + + "
    \n"); } @Test public void looseEmptyListItem() { - assertRendering("* a\n" + - "*\n" + - "\n" + - "* c", - "
      \n" + - "
    • \n" + - "

      a

      \n" + - "
    • \n" + - "
    • \n" + - "
    • \n" + - "

      c

      \n" + - "
    • \n" + - "
    \n"); + assertRendering( + "* a\n" + "*\n" + "\n" + "* c", + "
      \n" + + "
    • \n" + + "

      a

      \n" + + "
    • \n" + + "
    • \n" + + "
    • \n" + + "

      c

      \n" + + "
    • \n" + + "
    \n"); } @Test public void looseBlankLineAfterCodeBlock() { - assertRendering("1. ```\n" + - " foo\n" + - " ```\n" + - "\n" + - " bar", - "
      \n" + - "
    1. \n" + - "
      foo\n" +
      -                        "
      \n" + - "

      bar

      \n" + - "
    2. \n" + - "
    \n"); + assertRendering( + "1. ```\n" + " foo\n" + " ```\n" + "\n" + " bar", + "
      \n" + + "
    1. \n" + + "
      foo\n"
      +                        + "
      \n" + + "

      bar

      \n" + + "
    2. \n" + + "
    \n"); } } diff --git a/commonmark/src/test/java/org/commonmark/test/Nodes.java b/commonmark/src/test/java/org/commonmark/test/Nodes.java index 06d04fde6..6e96b11eb 100644 --- a/commonmark/src/test/java/org/commonmark/test/Nodes.java +++ b/commonmark/src/test/java/org/commonmark/test/Nodes.java @@ -1,10 +1,9 @@ package org.commonmark.test; -import org.commonmark.node.Node; - import java.util.ArrayList; import java.util.List; import java.util.Objects; +import org.commonmark.node.Node; public class Nodes { @@ -19,7 +18,7 @@ public static List getChildren(Node parent) { /** * Recursively try to find a node with the given type within the children of the specified node. * - * @param parent The node to get children from (node itself will not be checked) + * @param parent The node to get children from (node itself will not be checked) * @param nodeClass The type of node to find */ public static T tryFind(Node parent, Class nodeClass) { @@ -40,11 +39,12 @@ public static T tryFind(Node parent, Class nodeClass) { } /** - * Recursively try to find a node with the given type within the children of the specified node. Throw if node - * could not be found. + * Recursively try to find a node with the given type within the children of the specified node. + * Throw if node could not be found. */ public static T find(Node parent, Class nodeClass) { - return Objects.requireNonNull(tryFind(parent, nodeClass), + return Objects.requireNonNull( + tryFind(parent, nodeClass), "Could not find a " + nodeClass.getSimpleName() + " node in " + parent); } } diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 4348563bd..90bbd42d8 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -1,11 +1,7 @@ package org.commonmark.test; -import org.commonmark.node.*; -import org.commonmark.parser.*; -import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.renderer.markdown.MarkdownRenderer; -import org.commonmark.testutil.TestResources; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.io.IOException; import java.io.InputStream; @@ -16,9 +12,12 @@ import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.Future; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.commonmark.node.*; +import org.commonmark.parser.*; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.markdown.MarkdownRenderer; +import org.commonmark.testutil.TestResources; +import org.junit.jupiter.api.Test; public class ParserTest { @@ -62,8 +61,12 @@ public void enabledBlockTypes() { @Test public void enabledBlockTypesThrowsWhenGivenUnknownClass() { // BulletList can't be enabled separately at the moment, only all ListBlock types - assertThatThrownBy(() -> - Parser.builder().enabledBlockTypes(Set.of(Heading.class, BulletList.class)).build()).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy( + () -> + Parser.builder() + .enabledBlockTypes(Set.of(Heading.class, BulletList.class)) + .build()) + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -89,14 +92,16 @@ public void indentation() { @Test public void inlineParser() { - final InlineParser fakeInlineParser = (lines, node) -> node.appendChild(new ThematicBreak()); + final InlineParser fakeInlineParser = + (lines, node) -> node.appendChild(new ThematicBreak()); InlineParserFactory fakeInlineParserFactory = inlineParserContext -> fakeInlineParser; Parser parser = Parser.builder().inlineParserFactory(fakeInlineParserFactory).build(); String input = "**bold** **bold** ~~strikethrough~~"; - assertThat(parser.parse(input).getFirstChild().getFirstChild()).isInstanceOf(ThematicBreak.class); + assertThat(parser.parse(input).getFirstChild().getFirstChild()) + .isInstanceOf(ThematicBreak.class); } @Test @@ -123,8 +128,8 @@ public void threading() throws Exception { @Test public void maxOpenBlockParsersMustBeZeroOrGreater() { - assertThatThrownBy(() -> - Parser.builder().maxOpenBlockParsers(-1)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> Parser.builder().maxOpenBlockParsers(-1)) + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -169,11 +174,10 @@ public void maxOpenBlockParsersDegradesTheNinthLogicalListLevelToPlainText() { public void maxOpenBlockParsersAlsoLimitsMixedListAndBlockQuoteNesting() { var parser = Parser.builder().maxOpenBlockParsers(5).build(); - var document = parser.parse(String.join("\n", - "- level1", - " > level2", - " > > level3", - " > > > level4")); + var document = + parser.parse( + String.join( + "\n", "- level1", " > level2", " > > level3", " > > > level4")); var listBlock = document.getFirstChild(); assertThat(listBlock).isInstanceOf(BulletList.class); diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index 66d39de23..663aa7ced 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -1,15 +1,12 @@ package org.commonmark.test; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.Timeout; -import java.util.concurrent.TimeUnit; - -/** - * Pathological input cases (from commonmark.js). - */ +/** Pathological input cases (from commonmark.js). */ @Timeout(value = 3, unit = TimeUnit.SECONDS) @TestMethodOrder(MethodOrderer.MethodName.class) public class PathologicalTest extends CoreRenderingTestCase { @@ -22,50 +19,41 @@ public void nestedStrongEmphasis() { x = 500; assertRendering( "*a **a ".repeat(x) + "b" + " a** a*".repeat(x), - "

    " + "a a ".repeat(x) + "b" + - " a a".repeat(x) + "

    \n"); + "

    " + + "a a ".repeat(x) + + "b" + + " a a".repeat(x) + + "

    \n"); } @Test public void emphasisClosersWithNoOpeners() { - assertRendering( - "a_ ".repeat(x), - "

    " + "a_ ".repeat(x - 1) + "a_

    \n"); + assertRendering("a_ ".repeat(x), "

    " + "a_ ".repeat(x - 1) + "a_

    \n"); } @Test public void emphasisOpenersWithNoClosers() { - assertRendering( - "_a ".repeat(x), - "

    " + "_a ".repeat(x - 1) + "_a

    \n"); + assertRendering("_a ".repeat(x), "

    " + "_a ".repeat(x - 1) + "_a

    \n"); } @Test public void linkClosersWithNoOpeners() { - assertRendering( - "a] ".repeat(x), - "

    " + "a] ".repeat(x - 1) + "a]

    \n"); + assertRendering("a] ".repeat(x), "

    " + "a] ".repeat(x - 1) + "a]

    \n"); } @Test public void linkOpenersWithNoClosers() { - assertRendering( - "[a ".repeat(x), - "

    " + "[a ".repeat(x - 1) + "[a

    \n"); + assertRendering("[a ".repeat(x), "

    " + "[a ".repeat(x - 1) + "[a

    \n"); } @Test public void linkOpenersAndEmphasisClosers() { - assertRendering( - "[ a_ ".repeat(x), - "

    " + "[ a_ ".repeat(x - 1) + "[ a_

    \n"); + assertRendering("[ a_ ".repeat(x), "

    " + "[ a_ ".repeat(x - 1) + "[ a_

    \n"); } @Test public void mismatchedOpenersAndClosers() { - assertRendering( - "*a_ ".repeat(x), - "

    " + "*a_ ".repeat(x - 1) + "*a_

    \n"); + assertRendering("*a_ ".repeat(x), "

    " + "*a_ ".repeat(x - 1) + "*a_

    \n"); } @Test @@ -81,28 +69,23 @@ public void nestedBlockQuotes() { x = 1000; assertRendering( "> ".repeat(x) + "a\n", - "
    \n".repeat(x) + "

    a

    \n" + - "
    \n".repeat(x)); + "
    \n".repeat(x) + "

    a

    \n" + "
    \n".repeat(x)); } @Test public void hugeHorizontalRule() { - assertRendering( - "*".repeat(10000) + "\n", - "
    \n"); + assertRendering("*".repeat(10000) + "\n", "
    \n"); } @Test public void backslashInLink() { // See https://github.com/commonmark/commonmark.js/issues/157 - assertRendering("[" + "\\".repeat(x) + "\n", - "

    " + "[" + "\\".repeat(x / 2) + "

    \n"); + assertRendering("[" + "\\".repeat(x) + "\n", "

    " + "[" + "\\".repeat(x / 2) + "

    \n"); } @Test public void unclosedInlineLinks() { // See https://github.com/commonmark/commonmark.js/issues/129 - assertRendering("[](".repeat(x) + "\n", - "

    " + "[](".repeat(x) + "

    \n"); + assertRendering("[](".repeat(x) + "\n", "

    " + "[](".repeat(x) + "

    \n"); } } diff --git a/commonmark/src/test/java/org/commonmark/test/RegressionTest.java b/commonmark/src/test/java/org/commonmark/test/RegressionTest.java index 900a6518c..a7cc0eabf 100644 --- a/commonmark/src/test/java/org/commonmark/test/RegressionTest.java +++ b/commonmark/src/test/java/org/commonmark/test/RegressionTest.java @@ -1,5 +1,9 @@ package org.commonmark.test; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; @@ -11,23 +15,18 @@ import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.MethodSource; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - @ParameterizedClass @MethodSource("data") public class RegressionTest extends RenderingTestCase { private static final Parser PARSER = Parser.builder().build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().percentEncodeUrls(true).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().percentEncodeUrls(true).build(); private static final Map OVERRIDDEN_EXAMPLES = getOverriddenExamples(); - @Parameter - Example example; + @Parameter Example example; static List data() { var data = new ArrayList(); @@ -54,8 +53,10 @@ protected String render(String source) { private static Map getOverriddenExamples() { Map m = new HashMap<>(); - // The only difference is that we don't change `%28` and `%29` to `(` and `)` (percent encoding is preserved) - m.put("[XSS](javascript&colon;alert%28'XSS'%29)\n", + // The only difference is that we don't change `%28` and `%29` to `(` and `)` (percent + // encoding is preserved) + m.put( + "[XSS](javascript&colon;alert%28'XSS'%29)\n", "

    XSS

    \n"); // Callers should handle BOMs m.put("\uFEFF# Hi\n", "

    \uFEFF# Hi

    \n"); diff --git a/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java b/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java index 5d34bf410..d59e840fb 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java @@ -1,12 +1,12 @@ package org.commonmark.test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import org.commonmark.node.SourceSpan; import org.commonmark.parser.SourceLine; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - public class SourceLineTest { @Test @@ -29,16 +29,19 @@ public void testSubstring() { @Test public void testSubstringBeginOutOfBounds() { var sourceLine = SourceLine.of("abcd", SourceSpan.of(3, 10, 13, 4)); - assertThatThrownBy(() -> sourceLine.substring(3, 2)).isInstanceOf(StringIndexOutOfBoundsException.class); + assertThatThrownBy(() -> sourceLine.substring(3, 2)) + .isInstanceOf(StringIndexOutOfBoundsException.class); } @Test public void testSubstringEndOutOfBounds() { var sourceLine = SourceLine.of("abcd", SourceSpan.of(3, 10, 13, 4)); - assertThatThrownBy(() -> sourceLine.substring(0, 5)).isInstanceOf(StringIndexOutOfBoundsException.class); + assertThatThrownBy(() -> sourceLine.substring(0, 5)) + .isInstanceOf(StringIndexOutOfBoundsException.class); } - private static void assertSourceLine(SourceLine sourceLine, String expectedContent, SourceSpan expectedSourceSpan) { + private static void assertSourceLine( + SourceLine sourceLine, String expectedContent, SourceSpan expectedSourceSpan) { assertThat(sourceLine.getContent()).isEqualTo(expectedContent); assertThat(sourceLine.getSourceSpan()).isEqualTo(expectedSourceSpan); } diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java b/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java index 597a1e31c..3c6bf53ec 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java @@ -1,15 +1,12 @@ package org.commonmark.test; +import java.util.*; import org.commonmark.node.AbstractVisitor; import org.commonmark.node.Node; -import java.util.*; - public class SourceSpanRenderer { - /** - * Render source spans in the document using source position's line and column index. - */ + /** Render source spans in the document using source position's line and column index. */ public static String renderWithLineColumn(Node document, String source) { SourceSpanMarkersVisitor visitor = new SourceSpanMarkersVisitor(); document.accept(visitor); @@ -33,9 +30,7 @@ public static String renderWithLineColumn(Node document, String source) { return sb.toString(); } - /** - * Render source spans in the document using source position's input index. - */ + /** Render source spans in the document using source position's input index. */ public static String renderWithInputIndex(Node document, String source) { SourceSpanMarkersVisitor visitor = new SourceSpanMarkersVisitor(); document.accept(visitor); @@ -49,7 +44,8 @@ public static String renderWithInputIndex(Node document, String source) { return sb.toString(); } - private static void appendMarkers(Map> lineMarkers, int columnIndex, StringBuilder sb) { + private static void appendMarkers( + Map> lineMarkers, int columnIndex, StringBuilder sb) { if (lineMarkers != null) { List columnMarkers = lineMarkers.get(columnIndex); if (columnMarkers != null) { @@ -93,7 +89,9 @@ protected void visitChildren(Node parent) { getMarkers(line, col + length).add(0, closer); inputIndexMarkers.computeIfAbsent(input, k -> new LinkedList<>()).add(opener); - inputIndexMarkers.computeIfAbsent(input + length, k -> new LinkedList<>()).add(0, closer); + inputIndexMarkers + .computeIfAbsent(input + length, k -> new LinkedList<>()) + .add(0, closer); } markerIndex++; } diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpanTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpanTest.java index f1bb231f4..86e87cc98 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpanTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpanTest.java @@ -1,11 +1,11 @@ package org.commonmark.test; -import org.commonmark.node.SourceSpan; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.commonmark.node.SourceSpan; +import org.junit.jupiter.api.Test; + public class SourceSpanTest { @Test @@ -19,7 +19,8 @@ public void testSubSpan() { assertThat(span.subSpan(2)).isEqualTo(SourceSpan.of(1, 4, 5, 3)); assertThat(span.subSpan(3)).isEqualTo(SourceSpan.of(1, 5, 6, 2)); assertThat(span.subSpan(4)).isEqualTo(SourceSpan.of(1, 6, 7, 1)); - // Not sure if empty spans are useful, but it probably makes sense to mirror how substrings work + // Not sure if empty spans are useful, but it probably makes sense to mirror how substrings + // work assertThat(span.subSpan(5)).isEqualTo(SourceSpan.of(1, 7, 8, 0)); assertThat("abcde".substring(5)).isEqualTo(""); @@ -39,30 +40,35 @@ public void testSubSpan() { @Test public void testSubSpanBeginIndexNegative() { var sourceSpan = SourceSpan.of(1, 2, 3, 5); - assertThatThrownBy(() -> sourceSpan.subSpan(-1)).isInstanceOf(IndexOutOfBoundsException.class); + assertThatThrownBy(() -> sourceSpan.subSpan(-1)) + .isInstanceOf(IndexOutOfBoundsException.class); } @Test public void testSubSpanBeginIndexOutOfBounds() { var sourceSpan = SourceSpan.of(1, 2, 3, 5); - assertThatThrownBy(() -> sourceSpan.subSpan(6)).isInstanceOf(IndexOutOfBoundsException.class); + assertThatThrownBy(() -> sourceSpan.subSpan(6)) + .isInstanceOf(IndexOutOfBoundsException.class); } @Test public void testSubSpanEndIndexNegative() { var sourceSpan = SourceSpan.of(1, 2, 3, 5); - assertThatThrownBy(() -> sourceSpan.subSpan(0, -1)).isInstanceOf(IndexOutOfBoundsException.class); + assertThatThrownBy(() -> sourceSpan.subSpan(0, -1)) + .isInstanceOf(IndexOutOfBoundsException.class); } @Test public void testSubSpanEndIndexOutOfBounds() { var sourceSpan = SourceSpan.of(1, 2, 3, 5); - assertThatThrownBy(() -> sourceSpan.subSpan(0, 6)).isInstanceOf(IndexOutOfBoundsException.class); + assertThatThrownBy(() -> sourceSpan.subSpan(0, 6)) + .isInstanceOf(IndexOutOfBoundsException.class); } @Test public void testSubSpanBeginIndexGreaterThanEndIndex() { var sourceSpan = SourceSpan.of(1, 2, 3, 5); - assertThatThrownBy(() -> sourceSpan.subSpan(2, 1)).isInstanceOf(IndexOutOfBoundsException.class); + assertThatThrownBy(() -> sourceSpan.subSpan(2, 1)) + .isInstanceOf(IndexOutOfBoundsException.class); } } diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index f4e9d0a17..ceeff944a 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -1,31 +1,52 @@ package org.commonmark.test; -import org.commonmark.node.*; -import org.commonmark.parser.IncludeSourceSpans; -import org.commonmark.parser.Parser; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.io.StringReader; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; +import org.commonmark.node.*; +import org.commonmark.parser.IncludeSourceSpans; +import org.commonmark.parser.Parser; +import org.junit.jupiter.api.Test; public class SourceSpansTest { - private static final Parser PARSER = Parser.builder().includeSourceSpans(IncludeSourceSpans.BLOCKS).build(); - private static final Parser INLINES_PARSER = Parser.builder().includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES).build(); + private static final Parser PARSER = + Parser.builder().includeSourceSpans(IncludeSourceSpans.BLOCKS).build(); + private static final Parser INLINES_PARSER = + Parser.builder().includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES).build(); @Test public void paragraph() { assertSpans("foo\n", Paragraph.class, SourceSpan.of(0, 0, 0, 3)); - assertSpans("foo\nbar\n", Paragraph.class, SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 3)); - assertSpans(" foo\n bar\n", Paragraph.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 5)); - assertSpans("> foo\n> bar\n", Paragraph.class, SourceSpan.of(0, 2, 2, 3), SourceSpan.of(1, 2, 8, 3)); - assertSpans("* foo\n bar\n", Paragraph.class, SourceSpan.of(0, 2, 2, 3), SourceSpan.of(1, 2, 8, 3)); - assertSpans("* foo\nbar\n", Paragraph.class, SourceSpan.of(0, 2, 2, 3), SourceSpan.of(1, 0, 6, 3)); + assertSpans( + "foo\nbar\n", + Paragraph.class, + SourceSpan.of(0, 0, 0, 3), + SourceSpan.of(1, 0, 4, 3)); + assertSpans( + " foo\n bar\n", + Paragraph.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 5)); + assertSpans( + "> foo\n> bar\n", + Paragraph.class, + SourceSpan.of(0, 2, 2, 3), + SourceSpan.of(1, 2, 8, 3)); + assertSpans( + "* foo\n bar\n", + Paragraph.class, + SourceSpan.of(0, 2, 2, 3), + SourceSpan.of(1, 2, 8, 3)); + assertSpans( + "* foo\nbar\n", + Paragraph.class, + SourceSpan.of(0, 2, 2, 3), + SourceSpan.of(1, 0, 6, 3)); } @Test @@ -45,10 +66,24 @@ public void atxHeading() { @Test public void setextHeading() { - assertSpans("foo\n===\n", Heading.class, SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 3)); - assertSpans("foo\nbar\n====\n", Heading.class, SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 3), SourceSpan.of(2, 0, 8, 4)); - assertSpans(" foo\n ===\n", Heading.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 5)); - assertSpans("> foo\n> ===\n", Heading.class, SourceSpan.of(0, 2, 2, 3), SourceSpan.of(1, 2, 8, 3)); + assertSpans( + "foo\n===\n", Heading.class, SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 3)); + assertSpans( + "foo\nbar\n====\n", + Heading.class, + SourceSpan.of(0, 0, 0, 3), + SourceSpan.of(1, 0, 4, 3), + SourceSpan.of(2, 0, 8, 4)); + assertSpans( + " foo\n ===\n", + Heading.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 5)); + assertSpans( + "> foo\n> ===\n", + Heading.class, + SourceSpan.of(0, 2, 2, 3), + SourceSpan.of(1, 2, 8, 3)); } @Test @@ -63,30 +98,77 @@ public void indentedCodeBlock() { assertSpans(" \t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 9)); assertSpans("\t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 5)); assertSpans("\t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 6)); - assertSpans(" foo\n bar\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 8)); - assertSpans(" foo\n\tbar\n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 4)); - assertSpans(" foo\n \n \n", IndentedCodeBlock.class, SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 4), SourceSpan.of(2, 0, 13, 5)); + assertSpans( + " foo\n bar\n", + IndentedCodeBlock.class, + SourceSpan.of(0, 0, 0, 7), + SourceSpan.of(1, 0, 8, 8)); + assertSpans( + " foo\n\tbar\n", + IndentedCodeBlock.class, + SourceSpan.of(0, 0, 0, 7), + SourceSpan.of(1, 0, 8, 4)); + assertSpans( + " foo\n \n \n", + IndentedCodeBlock.class, + SourceSpan.of(0, 0, 0, 7), + SourceSpan.of(1, 0, 8, 4), + SourceSpan.of(2, 0, 13, 5)); assertSpans("> foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 2, 2, 7)); } @Test public void fencedCodeBlock() { - assertSpans("```\nfoo\n```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 3), SourceSpan.of(2, 0, 8, 3)); - assertSpans("```\n foo\n```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 4), SourceSpan.of(2, 0, 9, 3)); - assertSpans("```\nfoo\nbar\n```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 3), SourceSpan.of(2, 0, 8, 3), SourceSpan.of(3, 0, 12, 3)); - assertSpans(" ```\n foo\n ```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 0, 6), SourceSpan.of(1, 0, 7, 6), SourceSpan.of(2, 0, 14, 6)); - assertSpans(" ```\n foo\nfoo\n```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 0, 4), SourceSpan.of(1, 0, 5, 4), SourceSpan.of(2, 0, 10, 3), SourceSpan.of(3, 0, 14, 3)); - assertSpans("```info\nfoo\n```\n", FencedCodeBlock.class, - SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 3), SourceSpan.of(2, 0, 12, 3)); - assertSpans("* ```\n foo\n ```\n", FencedCodeBlock.class, - SourceSpan.of(0, 2, 2, 3), SourceSpan.of(1, 2, 8, 3), SourceSpan.of(2, 2, 14, 3)); - assertSpans("> ```\n> foo\n> ```\n", FencedCodeBlock.class, - SourceSpan.of(0, 2, 2, 3), SourceSpan.of(1, 2, 8, 3), SourceSpan.of(2, 2, 14, 3)); + assertSpans( + "```\nfoo\n```\n", + FencedCodeBlock.class, + SourceSpan.of(0, 0, 0, 3), + SourceSpan.of(1, 0, 4, 3), + SourceSpan.of(2, 0, 8, 3)); + assertSpans( + "```\n foo\n```\n", + FencedCodeBlock.class, + SourceSpan.of(0, 0, 0, 3), + SourceSpan.of(1, 0, 4, 4), + SourceSpan.of(2, 0, 9, 3)); + assertSpans( + "```\nfoo\nbar\n```\n", + FencedCodeBlock.class, + SourceSpan.of(0, 0, 0, 3), + SourceSpan.of(1, 0, 4, 3), + SourceSpan.of(2, 0, 8, 3), + SourceSpan.of(3, 0, 12, 3)); + assertSpans( + " ```\n foo\n ```\n", + FencedCodeBlock.class, + SourceSpan.of(0, 0, 0, 6), + SourceSpan.of(1, 0, 7, 6), + SourceSpan.of(2, 0, 14, 6)); + assertSpans( + " ```\n foo\nfoo\n```\n", + FencedCodeBlock.class, + SourceSpan.of(0, 0, 0, 4), + SourceSpan.of(1, 0, 5, 4), + SourceSpan.of(2, 0, 10, 3), + SourceSpan.of(3, 0, 14, 3)); + assertSpans( + "```info\nfoo\n```\n", + FencedCodeBlock.class, + SourceSpan.of(0, 0, 0, 7), + SourceSpan.of(1, 0, 8, 3), + SourceSpan.of(2, 0, 12, 3)); + assertSpans( + "* ```\n foo\n ```\n", + FencedCodeBlock.class, + SourceSpan.of(0, 2, 2, 3), + SourceSpan.of(1, 2, 8, 3), + SourceSpan.of(2, 2, 14, 3)); + assertSpans( + "> ```\n> foo\n> ```\n", + FencedCodeBlock.class, + SourceSpan.of(0, 2, 2, 3), + SourceSpan.of(1, 2, 8, 3), + SourceSpan.of(2, 2, 14, 3)); Node document = PARSER.parse("```\nfoo\n```\nbar\n"); Paragraph paragraph = (Paragraph) document.getLastChild(); @@ -96,7 +178,9 @@ public void fencedCodeBlock() { @Test public void htmlBlock() { assertSpans("
    \n", HtmlBlock.class, SourceSpan.of(0, 0, 0, 5)); - assertSpans("
    \n foo\n
    \n", HtmlBlock.class, + assertSpans( + "
    \n foo\n
    \n", + HtmlBlock.class, SourceSpan.of(0, 0, 0, 6), SourceSpan.of(1, 0, 7, 4), SourceSpan.of(2, 0, 12, 7)); @@ -109,27 +193,75 @@ public void blockQuote() { assertSpans("> foo\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 5)); assertSpans("> foo\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 6)); assertSpans(" > foo\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 6)); - assertSpans(" > foo\n > bar\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 8), SourceSpan.of(1, 0, 9, 7)); + assertSpans( + " > foo\n > bar\n", + BlockQuote.class, + SourceSpan.of(0, 0, 0, 8), + SourceSpan.of(1, 0, 9, 7)); // Lazy continuations - assertSpans("> foo\nbar\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 3)); - assertSpans("> foo\nbar\n> baz\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 3), SourceSpan.of(2, 0, 10, 5)); - assertSpans("> > foo\nbar\n", BlockQuote.class, SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 3)); + assertSpans( + "> foo\nbar\n", + BlockQuote.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 3)); + assertSpans( + "> foo\nbar\n> baz\n", + BlockQuote.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 3), + SourceSpan.of(2, 0, 10, 5)); + assertSpans( + "> > foo\nbar\n", + BlockQuote.class, + SourceSpan.of(0, 0, 0, 7), + SourceSpan.of(1, 0, 8, 3)); } @Test public void listBlock() { assertSpans("* foo\n", ListBlock.class, SourceSpan.of(0, 0, 0, 5)); - assertSpans("* foo\n bar\n", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 5)); - assertSpans("* foo\n* bar\n", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 5)); - assertSpans("* foo\n # bar\n", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 7)); - assertSpans("* foo\n * bar\n", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 7)); + assertSpans( + "* foo\n bar\n", + ListBlock.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 5)); + assertSpans( + "* foo\n* bar\n", + ListBlock.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 5)); + assertSpans( + "* foo\n # bar\n", + ListBlock.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 7)); + assertSpans( + "* foo\n * bar\n", + ListBlock.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 7)); assertSpans("* foo\n> bar\n", ListBlock.class, SourceSpan.of(0, 0, 0, 5)); assertSpans("> * foo\n", ListBlock.class, SourceSpan.of(0, 2, 2, 5)); // Lazy continuations - assertSpans("* foo\nbar\nbaz", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 3), SourceSpan.of(2, 0, 10, 3)); - assertSpans("* foo\nbar\n* baz", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 3), SourceSpan.of(2, 0, 10, 5)); - assertSpans("* foo\n * bar\nbaz", ListBlock.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 7), SourceSpan.of(2, 0, 14, 3)); + assertSpans( + "* foo\nbar\nbaz", + ListBlock.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 3), + SourceSpan.of(2, 0, 10, 3)); + assertSpans( + "* foo\nbar\n* baz", + ListBlock.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 3), + SourceSpan.of(2, 0, 10, 5)); + assertSpans( + "* foo\n * bar\nbaz", + ListBlock.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 7), + SourceSpan.of(2, 0, 14, 3)); Node document = PARSER.parse("* foo\n * bar\n"); ListBlock listBlock = (ListBlock) document.getFirstChild().getFirstChild().getLastChild(); @@ -142,23 +274,40 @@ public void listItem() { assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 0, 0, 6)); assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 0, 0, 7)); assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 0, 0, 8)); - assertSpans("*\n foo\n", ListItem.class, SourceSpan.of(0, 0, 0, 1), SourceSpan.of(1, 0, 2, 5)); - assertSpans("*\n foo\n bar\n", ListItem.class, SourceSpan.of(0, 0, 0, 1), SourceSpan.of(1, 0, 2, 5), SourceSpan.of(2, 0, 8, 5)); + assertSpans( + "*\n foo\n", ListItem.class, SourceSpan.of(0, 0, 0, 1), SourceSpan.of(1, 0, 2, 5)); + assertSpans( + "*\n foo\n bar\n", + ListItem.class, + SourceSpan.of(0, 0, 0, 1), + SourceSpan.of(1, 0, 2, 5), + SourceSpan.of(2, 0, 8, 5)); assertSpans("> * foo\n", ListItem.class, SourceSpan.of(0, 2, 2, 5)); // Lazy continuations - assertSpans("* foo\nbar\n", ListItem.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 3)); - assertSpans("* foo\nbar\nbaz\n", ListItem.class, SourceSpan.of(0, 0, 0, 5), SourceSpan.of(1, 0, 6, 3), SourceSpan.of(2, 0, 10, 3)); + assertSpans( + "* foo\nbar\n", + ListItem.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 3)); + assertSpans( + "* foo\nbar\nbaz\n", + ListItem.class, + SourceSpan.of(0, 0, 0, 5), + SourceSpan.of(1, 0, 6, 3), + SourceSpan.of(2, 0, 10, 3)); } @Test public void linkReferenceDefinition() { - // This is tricky due to how link reference definition parsing works. It is stripped from the paragraph if it's - // successfully parsed, otherwise it stays part of the paragraph. + // This is tricky due to how link reference definition parsing works. It is stripped from + // the paragraph if it's successfully parsed, otherwise it stays part of the paragraph. Node document = PARSER.parse("[foo]: /url\ntext\n"); - LinkReferenceDefinition linkReferenceDefinition = (LinkReferenceDefinition) document.getFirstChild(); - assertThat(linkReferenceDefinition.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 11))); + LinkReferenceDefinition linkReferenceDefinition = + (LinkReferenceDefinition) document.getFirstChild(); + assertThat(linkReferenceDefinition.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 0, 0, 11))); Paragraph paragraph = (Paragraph) document.getLastChild(); assertThat(paragraph.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(1, 0, 12, 4))); @@ -193,15 +342,18 @@ public void linkReferenceDefinitionWithTitleInvalid() { @Test public void linkReferenceDefinitionHeading() { - // This is probably the trickiest because we have a link reference definition at the start of a paragraph - // that gets replaced because of a heading. Phew. + // This is probably the trickiest because we have a link reference definition at the start + // of a paragraph that gets replaced because of a heading. Phew. Node document = PARSER.parse("[foo]: /url\nHeading\n===\n"); - LinkReferenceDefinition linkReferenceDefinition = (LinkReferenceDefinition) document.getFirstChild(); - assertThat(linkReferenceDefinition.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 11))); + LinkReferenceDefinition linkReferenceDefinition = + (LinkReferenceDefinition) document.getFirstChild(); + assertThat(linkReferenceDefinition.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 0, 0, 11))); Heading heading = (Heading) document.getLastChild(); - assertThat(heading.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(1, 0, 12, 7), SourceSpan.of(2, 0, 20, 3))); + assertThat(heading.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(1, 0, 12, 7), SourceSpan.of(2, 0, 20, 3))); } @Test @@ -212,13 +364,17 @@ public void lazyContinuationLines() { var doc = PARSER.parse("> > > foo\nbar\n"); var bq1 = (BlockQuote) doc.getLastChild(); - assertThat(bq1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 9), SourceSpan.of(1, 0, 10, 3))); + assertThat(bq1.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 0, 0, 9), SourceSpan.of(1, 0, 10, 3))); var bq2 = (BlockQuote) bq1.getLastChild(); - assertThat(bq2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 2, 2, 7), SourceSpan.of(1, 0, 10, 3))); + assertThat(bq2.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 2, 2, 7), SourceSpan.of(1, 0, 10, 3))); var bq3 = (BlockQuote) bq2.getLastChild(); - assertThat(bq3.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 4, 4, 5), SourceSpan.of(1, 0, 10, 3))); + assertThat(bq3.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 4, 4, 5), SourceSpan.of(1, 0, 10, 3))); var paragraph = (Paragraph) bq3.getLastChild(); - assertThat(paragraph.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 6, 6, 3), SourceSpan.of(1, 0, 10, 3))); + assertThat(paragraph.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 6, 6, 3), SourceSpan.of(1, 0, 10, 3))); } { @@ -226,13 +382,17 @@ public void lazyContinuationLines() { var doc = PARSER.parse("> > > foo\nbars\n"); var bq1 = (BlockQuote) doc.getLastChild(); - assertThat(bq1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 9), SourceSpan.of(1, 0, 10, 4))); + assertThat(bq1.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 0, 0, 9), SourceSpan.of(1, 0, 10, 4))); var bq2 = (BlockQuote) bq1.getLastChild(); - assertThat(bq2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 2, 2, 7), SourceSpan.of(1, 0, 10, 4))); + assertThat(bq2.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 2, 2, 7), SourceSpan.of(1, 0, 10, 4))); var bq3 = (BlockQuote) bq2.getLastChild(); - assertThat(bq3.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 4, 4, 5), SourceSpan.of(1, 0, 10, 4))); + assertThat(bq3.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 4, 4, 5), SourceSpan.of(1, 0, 10, 4))); var paragraph = (Paragraph) bq3.getLastChild(); - assertThat(paragraph.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 6, 6, 3), SourceSpan.of(1, 0, 10, 4))); + assertThat(paragraph.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 6, 6, 3), SourceSpan.of(1, 0, 10, 4))); } { @@ -240,15 +400,20 @@ public void lazyContinuationLines() { var doc = PARSER.parse("> 1. > Blockquote\ncontinued here."); var bq1 = (BlockQuote) doc.getLastChild(); - assertThat(bq1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 17), SourceSpan.of(1, 0, 18, 15))); + assertThat(bq1.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 0, 0, 17), SourceSpan.of(1, 0, 18, 15))); var orderedList = (OrderedList) bq1.getLastChild(); - assertThat(orderedList.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 2, 2, 15), SourceSpan.of(1, 0, 18, 15))); + assertThat(orderedList.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 2, 2, 15), SourceSpan.of(1, 0, 18, 15))); var listItem = (ListItem) orderedList.getLastChild(); - assertThat(listItem.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 2, 2, 15), SourceSpan.of(1, 0, 18, 15))); + assertThat(listItem.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 2, 2, 15), SourceSpan.of(1, 0, 18, 15))); var bq2 = (BlockQuote) listItem.getLastChild(); - assertThat(bq2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 5, 5, 12), SourceSpan.of(1, 0, 18, 15))); + assertThat(bq2.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 5, 5, 12), SourceSpan.of(1, 0, 18, 15))); var paragraph = (Paragraph) bq2.getLastChild(); - assertThat(paragraph.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 7, 7, 10), SourceSpan.of(1, 0, 18, 15))); + assertThat(paragraph.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 7, 7, 10), SourceSpan.of(1, 0, 18, 15))); } { @@ -256,18 +421,25 @@ public void lazyContinuationLines() { var doc = PARSER.parse("> > foo\n> bar\n"); var bq1 = (BlockQuote) doc.getLastChild(); - assertThat(bq1.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 5))); + assertThat(bq1.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 0, 0, 7), SourceSpan.of(1, 0, 8, 5))); var bq2 = (BlockQuote) bq1.getLastChild(); - assertThat(bq2.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 2, 2, 5), SourceSpan.of(1, 2, 10, 3))); + assertThat(bq2.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 2, 2, 5), SourceSpan.of(1, 2, 10, 3))); var paragraph = (Paragraph) bq2.getLastChild(); - assertThat(paragraph.getSourceSpans()).isEqualTo(List.of(SourceSpan.of(0, 4, 4, 3), SourceSpan.of(1, 2, 10, 3))); + assertThat(paragraph.getSourceSpans()) + .isEqualTo(List.of(SourceSpan.of(0, 4, 4, 3), SourceSpan.of(1, 2, 10, 3))); } } @Test public void visualCheck() { - assertVisualize("> * foo\n> bar\n> * baz\n", "(> {[* ]})\n(> {[ ]})\n(> {⸢* ⸤baz⸥⸣})\n"); - assertVisualize("> * ```\n> foo\n> ```\n", "(> {[* <```>]})\n(> {[ ]})\n(> {[ <```>]})\n"); + assertVisualize( + "> * foo\n> bar\n> * baz\n", + "(> {[* ]})\n(> {[ ]})\n(> {⸢* ⸤baz⸥⸣})\n"); + assertVisualize( + "> * ```\n> foo\n> ```\n", + "(> {[* <```>]})\n(> {[ ]})\n(> {[ <```>]})\n"); } @Test @@ -276,7 +448,8 @@ public void inlineText() { assertInlineSpans("> foo", Text.class, SourceSpan.of(0, 2, 2, 3)); assertInlineSpans("* foo", Text.class, SourceSpan.of(0, 2, 2, 3)); - // SourceSpans should be merged: ` is a separate Text node while inline parsing and gets merged at the end + // SourceSpans should be merged: ` is a separate Text node while inline parsing and gets + // merged at the end assertInlineSpans("foo`bar", Text.class, SourceSpan.of(0, 0, 0, 7)); assertInlineSpans("foo[bar", Text.class, SourceSpan.of(0, 0, 0, 7)); assertInlineSpans("> foo`bar", Text.class, SourceSpan.of(0, 2, 2, 7)); @@ -305,9 +478,8 @@ public void inlineBackslash() { @Test public void inlineBackticks() { assertInlineSpans("see `code`", Code.class, SourceSpan.of(0, 4, 4, 6)); - assertInlineSpans("`multi\nline`", Code.class, - SourceSpan.of(0, 0, 0, 6), - SourceSpan.of(1, 0, 7, 5)); + assertInlineSpans( + "`multi\nline`", Code.class, SourceSpan.of(0, 0, 0, 6), SourceSpan.of(1, 0, 7, 5)); assertInlineSpans("text ```", Text.class, SourceSpan.of(0, 0, 0, 8)); } @@ -368,13 +540,14 @@ public void tabExpansion() { @Test public void differentLineTerminators() { var input = "foo\nbar\rbaz\r\nqux\r\n\r\n> *hi*"; - assertSpans(input, Paragraph.class, + assertSpans( + input, + Paragraph.class, SourceSpan.of(0, 0, 0, 3), SourceSpan.of(1, 0, 4, 3), SourceSpan.of(2, 0, 8, 3), SourceSpan.of(3, 0, 13, 3)); - assertSpans(input, BlockQuote.class, - SourceSpan.of(5, 0, 20, 6)); + assertSpans(input, BlockQuote.class, SourceSpan.of(5, 0, 20, 6)); assertInlineSpans(input, Emphasis.class, SourceSpan.of(5, 2, 22, 4)); } @@ -385,25 +558,32 @@ private void assertVisualize(String source, String expected) { assertThat(SourceSpanRenderer.renderWithInputIndex(doc, source)).isEqualTo(expected); } - private static void assertSpans(String input, Class nodeClass, SourceSpan... expectedSourceSpans) { + private static void assertSpans( + String input, Class nodeClass, SourceSpan... expectedSourceSpans) { assertSpans(PARSER.parse(input), nodeClass, expectedSourceSpans); try { - assertSpans(PARSER.parseReader(new StringReader(input)), nodeClass, expectedSourceSpans); + assertSpans( + PARSER.parseReader(new StringReader(input)), nodeClass, expectedSourceSpans); } catch (IOException e) { throw new RuntimeException(e); } } - private static void assertInlineSpans(String input, Class nodeClass, SourceSpan... expectedSourceSpans) { + private static void assertInlineSpans( + String input, Class nodeClass, SourceSpan... expectedSourceSpans) { assertSpans(INLINES_PARSER.parse(input), nodeClass, expectedSourceSpans); try { - assertSpans(INLINES_PARSER.parseReader(new StringReader(input)), nodeClass, expectedSourceSpans); + assertSpans( + INLINES_PARSER.parseReader(new StringReader(input)), + nodeClass, + expectedSourceSpans); } catch (IOException e) { throw new RuntimeException(e); } } - private static void assertSpans(Node rootNode, Class nodeClass, SourceSpan... expectedSourceSpans) { + private static void assertSpans( + Node rootNode, Class nodeClass, SourceSpan... expectedSourceSpans) { Node node = findNode(rootNode, nodeClass); assertThat(node.getSourceSpans()).isEqualTo(List.of(expectedSourceSpans)); } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java index e7bb080a8..f116404f9 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java @@ -1,5 +1,6 @@ package org.commonmark.test; +import java.util.List; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; @@ -11,8 +12,6 @@ import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; -import java.util.List; - @State(Scope.Benchmark) @Fork(5) @Warmup(iterations = 10) @@ -20,17 +19,19 @@ public class SpecBenchmark { private static final String SPEC = TestResources.readAsString(TestResources.getSpec()); - private static final List SPEC_EXAMPLES = ExampleReader.readExampleSources(TestResources.getSpec()); + private static final List SPEC_EXAMPLES = + ExampleReader.readExampleSources(TestResources.getSpec()); private static final Parser PARSER = Parser.builder().build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); private static final Node SPEC_NODE = PARSER.parse(SPEC); public static void main(String[] args) throws Exception { - Options options = new OptionsBuilder() - .parent(new CommandLineOptions(args)) - .include(SpecBenchmark.class.getName() + ".*") - .build(); + Options options = + new OptionsBuilder() + .parent(new CommandLineOptions(args)) + .include(SpecBenchmark.class.getName() + ".*") + .build(); new Runner(options).run(); } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java index 047a1e6cf..d7963da5f 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java @@ -1,5 +1,8 @@ package org.commonmark.test; +import static org.assertj.core.api.Assertions.fail; +import static org.commonmark.testutil.Asserts.assertRendering; + import org.commonmark.node.AbstractVisitor; import org.commonmark.node.Node; import org.commonmark.node.Text; @@ -8,41 +11,47 @@ import org.commonmark.testutil.SpecTestCase; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.fail; -import static org.commonmark.testutil.Asserts.assertRendering; - public class SpecCoreTest extends SpecTestCase { private static final Parser PARSER = Parser.builder().build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().percentEncodeUrls(true).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().percentEncodeUrls(true).build(); @Test public void testTextNodesContiguous() { final String source = example.getSource(); Node node = PARSER.parse(source); - node.accept(new AbstractVisitor() { - @Override - protected void visitChildren(Node parent) { - if (parent instanceof Text && parent.getFirstChild() != null) { - fail("Text node is not allowed to have children, literal is \"" + ((Text) parent).getLiteral() + "\""); - } - boolean lastText = false; - Node node = parent.getFirstChild(); - while (node != null) { - if (node instanceof Text) { - if (lastText) { - fail("Adjacent text nodes found, second node literal is \"" + ((Text) node).getLiteral() + "\", source:\n" + source); + node.accept( + new AbstractVisitor() { + @Override + protected void visitChildren(Node parent) { + if (parent instanceof Text && parent.getFirstChild() != null) { + fail( + "Text node is not allowed to have children, literal is \"" + + ((Text) parent).getLiteral() + + "\""); + } + boolean lastText = false; + Node node = parent.getFirstChild(); + while (node != null) { + if (node instanceof Text) { + if (lastText) { + fail( + "Adjacent text nodes found, second node literal is \"" + + ((Text) node).getLiteral() + + "\", source:\n" + + source); + } + lastText = true; + } else { + lastText = false; + } + node = node.getNext(); } - lastText = true; - } else { - lastText = false; + super.visitChildren(parent); } - node = node.getNext(); - } - super.visitChildren(parent); - } - }); + }); } @Test diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java index eb0fcd0dd..ac2c70890 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java @@ -1,20 +1,22 @@ package org.commonmark.test; +import static org.commonmark.testutil.Asserts.assertRendering; + import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.SpecTestCase; import org.junit.jupiter.api.Test; -import static org.commonmark.testutil.Asserts.assertRendering; - /** - * Same as {@link SpecCoreTest} but converts line endings to Windows-style CR+LF endings before parsing. + * Same as {@link SpecCoreTest} but converts line endings to Windows-style CR+LF endings before + * parsing. */ public class SpecCrLfCoreTest extends SpecTestCase { private static final Parser PARSER = Parser.builder().build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().percentEncodeUrls(true).build(); + private static final HtmlRenderer RENDERER = + HtmlRenderer.builder().percentEncodeUrls(true).build(); @Test public void testHtmlRendering() { diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 45cd3aea2..f252a397f 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -31,8 +31,11 @@ public void crLfAtEndShouldBeParsed() { @Test public void mixedLineSeparators() { - assertRendering("- a\n- b\r- c\r\n- d", "
      \n
    • a
    • \n
    • b
    • \n
    • c
    • \n
    • d
    • \n
    \n"); - assertRendering("a\n\nb\r\rc\r\n\r\nd\n\re", "

    a

    \n

    b

    \n

    c

    \n

    d

    \n

    e

    \n"); + assertRendering( + "- a\n- b\r- c\r\n- d", + "
      \n
    • a
    • \n
    • b
    • \n
    • c
    • \n
    • d
    • \n
    \n"); + assertRendering( + "a\n\nb\r\rc\r\n\r\nd\n\re", "

    a

    \n

    b

    \n

    c

    \n

    d

    \n

    e

    \n"); } @Test @@ -52,13 +55,16 @@ public void indentedCodeBlockWithMixedTabsAndSpaces() { @Test public void tightListInBlockQuote() { - assertRendering("> *\n> * a", "
    \n
      \n
    • \n
    • a
    • \n
    \n
    \n"); + assertRendering( + "> *\n> * a", "
    \n
      \n
    • \n
    • a
    • \n
    \n
    \n"); } @Test public void looseListInBlockQuote() { // Second line in block quote is considered blank for purpose of loose list - assertRendering("> *\n>\n> * a", "
    \n
      \n
    • \n
    • \n

      a

      \n
    • \n
    \n
    \n"); + assertRendering( + "> *\n>\n> * a", + "
    \n
      \n
    • \n
    • \n

      a

      \n
    • \n
    \n
    \n"); } @Test @@ -68,7 +74,8 @@ public void lineWithOnlySpacesAfterListBullet() { @Test public void listWithTwoSpacesForFirstBullet() { - // We have two spaces after the bullet, but no content. With content, the next line would be required + // We have two spaces after the bullet, but no content. With content, the next line would be + // required assertRendering("* \n foo\n", "
      \n
    • foo
    • \n
    \n"); } @@ -79,9 +86,11 @@ public void orderedListMarkerOnly() { @Test public void columnIsInTabOnPreviousLine() { - assertRendering("- foo\n\n\tbar\n\n# baz\n", + assertRendering( + "- foo\n\n\tbar\n\n# baz\n", "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n

    baz

    \n"); - assertRendering("- foo\n\n\tbar\n# baz\n", + assertRendering( + "- foo\n\n\tbar\n# baz\n", "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n

    baz

    \n"); } @@ -95,15 +104,20 @@ public void linkLabelWithBracket() { @Test public void linkLabelLength() { String label1 = "a".repeat(999); - assertRendering("[foo][" + label1 + "]\n\n[" + label1 + "]: /", "

    foo

    \n"); - assertRendering("[foo][x" + label1 + "]\n\n[x" + label1 + "]: /", + assertRendering( + "[foo][" + label1 + "]\n\n[" + label1 + "]: /", "

    foo

    \n"); + assertRendering( + "[foo][x" + label1 + "]\n\n[x" + label1 + "]: /", "

    [foo][x" + label1 + "]

    \n

    [x" + label1 + "]: /

    \n"); - assertRendering("[foo][\n" + label1 + "]\n\n[\n" + label1 + "]: /", + assertRendering( + "[foo][\n" + label1 + "]\n\n[\n" + label1 + "]: /", "

    [foo][\n" + label1 + "]

    \n

    [\n" + label1 + "]: /

    \n"); String label2 = "a\n".repeat(499); - assertRendering("[foo][" + label2 + "]\n\n[" + label2 + "]: /", "

    foo

    \n"); - assertRendering("[foo][12" + label2 + "]\n\n[12" + label2 + "]: /", + assertRendering( + "[foo][" + label2 + "]\n\n[" + label2 + "]: /", "

    foo

    \n"); + assertRendering( + "[foo][12" + label2 + "]\n\n[12" + label2 + "]: /", "

    [foo][12" + label2 + "]

    \n

    [12" + label2 + "]: /

    \n"); } @@ -111,7 +125,8 @@ public void linkLabelLength() { public void linkDestinationEscaping() { // Backslash escapes `)` assertRendering("[foo](\\))", "

    foo

    \n"); - // ` ` is not escapable, so the backslash is a literal backslash and there's an optional space at the end + // ` ` is not escapable, so the backslash is a literal backslash and there's an optional + // space at the end assertRendering("[foo](\\ )", "

    foo

    \n"); // Backslash is a literal, so valid assertRendering("[foo]()", "

    foo

    \n"); @@ -141,35 +156,35 @@ public void emphasisMultipleOf3Rule() { @Test public void renderEvenRegexpProducesStackoverflow() { - render("Contents: `, ``, or ``, and `-->` (see the [HTML // spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)). + if (noCommentEnd) { + // Both `` and `` contain `-->` themselves, so this can't skip a valid short + // comment either. + return false; + } + // Skip first `-` scanner.next(); if (!scanner.next('-')) { @@ -182,13 +206,19 @@ private static boolean tryComment(Scanner scanner) { } } + // The loop above only ends when the scan reached the end of the input. + noCommentEnd = true; return false; } - private static boolean tryCdata(Scanner scanner) { + private boolean tryCdata(Scanner scanner) { // spec: A CDATA section consists of the string , and the string ]]>. + if (noCdataEnd) { + return false; + } + // Skip `[` scanner.next(); @@ -200,14 +230,19 @@ private static boolean tryCdata(Scanner scanner) { scanner.next(); } } + // The loop above only ends when the scan reached the end of the input. + noCdataEnd = true; } return false; } - private static boolean tryDeclaration(Scanner scanner) { + private boolean tryDeclaration(Scanner scanner) { // spec: A declaration consists of the string , and the character >. + if (noDeclarationEnd) { + return false; + } scanner.match(asciiLetter); if (scanner.whitespace() <= 0) { return false; @@ -216,6 +251,8 @@ private static boolean tryDeclaration(Scanner scanner) { scanner.next(); return true; } + // `find` only returns a negative value when it reached the end of the input. + noDeclarationEnd = true; return false; } diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java index 8e1fd9790..454c1de8b 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java @@ -21,6 +21,28 @@ public void cdata() { assertRendering("inline ", "

    inline

    \n"); } + @Test + public void afterFailedAttempt() { + // A `<` that doesn't start inline HTML must not stop a later one from being parsed + assertRendering("inline ", "

    inline <!-

    \n"); + assertRendering("inline ", "

    inline <??

    \n"); + assertRendering( + "inline ", "

    inline <![x]]>

    \n"); + assertRendering("inline ", "

    inline <!foo>

    \n"); + } + + @Test + public void unterminatedConstructDoesNotAffectLaterParagraph() { + // A scan that reaches the end of the input only says something about the inline snippet it + // ran in, and a parser is created for each of those + assertRendering("x ", "

    x <?a

    \n

    x

    \n"); + assertRendering("x ", "

    x <!--a

    \n

    x

    \n"); + assertRendering( + "x ", + "

    x <![CDATA[a

    \n

    x

    \n"); + assertRendering("x ", "

    x <!A a

    \n

    x

    \n"); + } + @Test public void declaration() { // Whitespace is mandatory diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index 663aa7ced..8ef6de344 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -88,4 +88,29 @@ public void unclosedInlineLinks() { // See https://github.com/commonmark/commonmark.js/issues/129 assertRendering("[](".repeat(x) + "\n", "

    " + "[](".repeat(x) + "

    \n"); } + + // The following cases each start an inline HTML construct whose terminator never occurs, so + // every occurrence used to scan the rest of the input again. They all contain a `>` close to + // each `<` so that the separate autolink scan for `>` stays cheap and only the inline HTML + // scanning is measured. The leading text keeps the line from starting with `<`, which would + // parse as an HTML block instead. + + @Test + public void htmlProcessingInstructionsWithNoEnd() { + // `?>` never occurs because of the space + assertRendering("x ".repeat(x), "

    " + "x <? >".repeat(x) + "

    \n"); + } + + @Test + public void htmlCommentsWithNoEnd() { + // `-->` never occurs because of the space + assertRendering("x From bd3e06669fd168874721267e7e5f5bd1d3433466 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 6 Aug 2026 11:32:47 +1000 Subject: [PATCH 338/345] Fix Javadoc --- .../java/org/commonmark/ext/gfm/tables/TablesExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java index f0fef5dce..0db0ea513 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java @@ -32,7 +32,7 @@ public class TablesExtension TextContentRenderer.TextContentRendererExtension, MarkdownRenderer.MarkdownRendererExtension { - /** The default limit for the maximum number of cells, see {@link Builder#maxCells(int)}. */ + /** The default limit for the maximum number of cells, see {@link Builder#maxCells(Integer)}. */ public static final int DEFAULT_MAX_CELLS = 1_000_000; private final Integer maxCells; From 64037cf982f2b12a4a2fa1c5a75c6d3f4ab979bf Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Thu, 6 Aug 2026 01:36:21 +0000 Subject: [PATCH 339/345] [maven-release-plugin] prepare release commonmark-parent-0.30.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-alerts/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 28 ++++++++++++------------ 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 9aa41aca6..1736dfa0b 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index b2ebba9f8..7ef148471 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-alerts/pom.xml b/commonmark-ext-gfm-alerts/pom.xml index a90f7fddf..82ad43539 100644 --- a/commonmark-ext-gfm-alerts/pom.xml +++ b/commonmark-ext-gfm-alerts/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark-ext-gfm-alerts diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 107dc7642..a3fca4e87 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index ace2203ad..2757a73ac 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 9b09d861f..41753986a 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 5c9e4e82c..f02ab2f84 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 225407558..fbb8714c7 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 0d3824353..6a34dffa6 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 1b467e594..0bc691055 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.30.0-SNAPSHOT + 0.30.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 5b077c46a..50525d909 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index efaf55086..9f2e97ec9 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index d3221dbf9..f4e6dc418 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark diff --git a/pom.xml b/pom.xml index af549dac9..eba464f6e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.30.0-SNAPSHOT + 0.30.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -145,62 +145,62 @@ org.commonmark commonmark - 0.30.0-SNAPSHOT + 0.30.0 org.commonmark commonmark-ext-autolink - 0.30.0-SNAPSHOT + 0.30.0 org.commonmark commonmark-ext-footnotes - 0.30.0-SNAPSHOT + 0.30.0 org.commonmark commonmark-ext-image-attributes - 0.30.0-SNAPSHOT + 0.30.0 org.commonmark commonmark-ext-ins - 0.30.0-SNAPSHOT + 0.30.0 org.commonmark commonmark-ext-gfm-alerts - 0.30.0-SNAPSHOT + 0.30.0 org.commonmark commonmark-ext-gfm-strikethrough - 0.30.0-SNAPSHOT + 0.30.0 org.commonmark commonmark-ext-gfm-tables - 0.30.0-SNAPSHOT + 0.30.0 org.commonmark commonmark-ext-heading-anchor - 0.30.0-SNAPSHOT + 0.30.0 org.commonmark commonmark-ext-task-list-items - 0.30.0-SNAPSHOT + 0.30.0 org.commonmark commonmark-ext-yaml-front-matter - 0.30.0-SNAPSHOT + 0.30.0 org.commonmark commonmark-test-util - 0.30.0-SNAPSHOT + 0.30.0 @@ -327,7 +327,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - HEAD + commonmark-parent-0.30.0 From 64cfce472b3a37162cb2cea5c722cfc2be41af0c Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Thu, 6 Aug 2026 01:36:24 +0000 Subject: [PATCH 340/345] [maven-release-plugin] prepare for next development iteration --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-alerts/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 28 ++++++++++++------------ 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 1736dfa0b..af2b99363 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 7ef148471..7300078b2 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-alerts/pom.xml b/commonmark-ext-gfm-alerts/pom.xml index 82ad43539..ec7467f85 100644 --- a/commonmark-ext-gfm-alerts/pom.xml +++ b/commonmark-ext-gfm-alerts/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark-ext-gfm-alerts diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index a3fca4e87..a3bdf38ee 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 2757a73ac..ea35aaf14 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 41753986a..330356418 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index f02ab2f84..f48904c57 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index fbb8714c7..b275a4e93 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 6a34dffa6..b8d708a26 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 0bc691055..d3156892b 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.30.0 + 0.30.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 50525d909..d16b7ea05 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 9f2e97ec9..3eb723f4e 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index f4e6dc418..30125aa0b 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index eba464f6e..359a85143 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.30.0 + 0.30.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -145,62 +145,62 @@ org.commonmark commonmark - 0.30.0 + 0.30.1-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.30.0 + 0.30.1-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.30.0 + 0.30.1-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.30.0 + 0.30.1-SNAPSHOT org.commonmark commonmark-ext-ins - 0.30.0 + 0.30.1-SNAPSHOT org.commonmark commonmark-ext-gfm-alerts - 0.30.0 + 0.30.1-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.30.0 + 0.30.1-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.30.0 + 0.30.1-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.30.0 + 0.30.1-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.30.0 + 0.30.1-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.30.0 + 0.30.1-SNAPSHOT org.commonmark commonmark-test-util - 0.30.0 + 0.30.1-SNAPSHOT @@ -327,7 +327,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - commonmark-parent-0.30.0 + HEAD From 5f472f7de5e11850a4d28f9ccc947d80343b705d Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 6 Aug 2026 21:19:39 +1000 Subject: [PATCH 341/345] README: Bump version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f9a03fb0a..14e237afa 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.29.0 + 0.30.0 ``` @@ -293,7 +293,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.29.0 + 0.30.0 ``` From 104a8ce82a1d9b6a6bbf76133818263ce7c90537 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 7 Aug 2026 12:13:16 +1000 Subject: [PATCH 342/345] Update GitHub actions --- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/release.yml | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b32794271..95bdda997 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,10 +12,10 @@ jobs: java: [11, 17, 21, 25] steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: ${{ matrix.java }} distribution: 'zulu' @@ -28,10 +28,10 @@ jobs: if: ${{ github.event_name == 'push' }} steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 11 distribution: 'zulu' @@ -40,7 +40,7 @@ jobs: run: mvn -B -Pcoverage clean test jacoco:report-aggregate - name: Publish coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v7 with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} @@ -49,10 +49,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 11 distribution: 'zulu' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c0531ca55..dcf5b3164 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,10 +14,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Maven Central repository - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 24 distribution: 'zulu' From c17c4aedfbe67f51c4e30f0acd8e73496179905e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 7 Aug 2026 13:55:58 +1000 Subject: [PATCH 343/345] README: Update users --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 14e237afa..2f4623c49 100644 --- a/README.md +++ b/README.md @@ -492,15 +492,17 @@ Used by Some users of this library (feel free to raise a PR if you want to be added): * [Atlassian](https://www.atlassian.com/) (where the library was initially developed) -* Java (OpenJDK) ([link](https://github.com/openjdk/jdk/blob/3895b8fc0b2c6d187080dba6fe08297adad4a480/src/jdk.internal.md/share/classes/module-info.java)) -* [Gerrit](https://www.gerritcodereview.com/) code review/Gitiles ([link](https://gerrit-review.googlesource.com/c/gitiles/+/353794)) +* Java (OpenJDK) ([code](https://github.com/openjdk/jdk/blob/3895b8fc0b2c6d187080dba6fe08297adad4a480/src/jdk.internal.md/share/classes/module-info.java)) +* [Gerrit](https://www.gerritcodereview.com/) code review/Gitiles ([code](https://gerrit-review.googlesource.com/c/gitiles/+/353794)) +* [google-java-format](https://github.com/google/google-java-format) Java formatter (for Markdown in Javadoc) +* [Apache Tika](https://github.com/apache/tika) toolkit for detecting and extracting metadata and structured text content from various documents * [Clerk](https://clerk.vision/) moldable live programming for Clojure * [Znai](https://github.com/testingisdocumenting/znai) -* [Open Note](https://github.com/YangDai2003/OpenNote-Compose) a markdown editor and note-taking app for Android -* [Quarkus Roq](https://github.com/quarkiverse/quarkus-roq/) The Roq Static Site Generator allows to easily create a static website or blog using Quarkus super-powers. -* [Lucee](https://github.com/lucee/lucee) -* [Previewer](https://github.com/sebthom/previewer-eclipse-plugin) an extensible Eclipse plugin that previews Markdown and other text based formats. -* [Xeres](https://xeres.io) a Peer-to-Peer application where all user generated content is done with markdown +* [Open Note](https://github.com/YangDai2003/OpenNote-Compose) markdown editor and note-taking app for Android +* [Quarkus Roq](https://github.com/quarkiverse/quarkus-roq/) static website or blog generator +* [Lucee](https://github.com/lucee/lucee) dynamic, Java based, tag and scripting language used for rapid web application development +* [Previewer](https://github.com/sebthom/previewer-eclipse-plugin) Eclipse plugin that previews Markdown and other text based formats +* [Xeres](https://xeres.io) peer-to-peer application where all user generated content is done with markdown See also -------- From 5c105c357ffbec25cd5609fdde5722c1f109f2f1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 7 Aug 2026 14:03:33 +1000 Subject: [PATCH 344/345] Use text blocks in tests (set testRelease to 17) --- .../commonmark/ext/autolink/AutolinkTest.java | 14 +- .../footnotes/FootnoteHtmlRendererTest.java | 485 +++-- .../commonmark/ext/gfm/alerts/AlertsTest.java | 633 ++++--- .../gfm/alerts/examples/AlertsExample.java | 48 +- .../commonmark/ext/gfm/tables/TablesTest.java | 1558 ++++++++++------- .../ext/heading/anchor/HeadingAnchorTest.java | 105 +- .../task/list/items/TaskListItemsTest.java | 313 +++- .../front/matter/YamlFrontMatterDataTest.java | 185 +- .../ExtensionsIntegrationTest.java | 21 +- .../java/org/commonmark/ui/DingusApp.java | 15 +- .../org/commonmark/test/DelimitedTest.java | 7 +- .../test/FencedCodeBlockParserTest.java | 75 +- .../commonmark/test/ListTightLooseTest.java | 280 ++- .../org/commonmark/test/SpecialInputTest.java | 339 +++- .../test/TextContentRendererTest.java | 465 ++++- pom.xml | 1 + 16 files changed, 3125 insertions(+), 1419 deletions(-) diff --git a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java index fb79dfd60..34e50ac3e 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -91,12 +91,14 @@ public void sourceSpans() { .extensions(EXTENSIONS) .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) .build(); - Node document = - parser.parse( - "abc\n" - + "http://example.com/one\n" - + "def http://example.com/two\n" - + "ghi http://example.com/three jkl"); + var s = + """ + abc + http://example.com/one + def http://example.com/two + ghi http://example.com/three jkl + """; + Node document = parser.parse(s); Paragraph paragraph = (Paragraph) document.getFirstChild(); Text abc = (Text) paragraph.getFirstChild(); diff --git a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java index 1a89693b4..9102083e3 100644 --- a/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java +++ b/commonmark-ext-footnotes/src/test/java/org/commonmark/ext/footnotes/FootnoteHtmlRendererTest.java @@ -21,15 +21,21 @@ public class FootnoteHtmlRendererTest extends RenderingTestCase { @Test public void testOne() { assertRendering( - "Test [^foo]\n\n[^foo]: note\n", - "

    Test 1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      note

      \n" - + "
    2. \n" - + "
    \n" - + "
    \n"); + """ + Test [^foo] + + [^foo]: note + """, + """ +

    Test 1

    +
    +
      +
    1. +

      note

      +
    2. +
    +
    + """); } @Test @@ -37,15 +43,21 @@ public void testLabelNormalization() { // Labels match via their normalized form. For the href and IDs to match, rendering needs to // use the label from the definition consistently. assertRendering( - "Test [^bar]\n\n[^BAR]: note\n", - "

    Test 1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      note

      \n" - + "
    2. \n" - + "
    \n" - + "
    \n"); + """ + Test [^bar] + + [^BAR]: note + """, + """ +

    Test 1

    +
    +
      +
    1. +

      note

      +
    2. +
    +
    + """); } @Test @@ -55,53 +67,80 @@ public void testMultipleReferences() { // - The same number is used when a definition is referenced multiple times // - Multiple backrefs are rendered assertRendering( - "First [^foo]\n\nThen [^bar]\n\nThen [^foo] again\n\n[^bar]: b\n[^foo]: f\n", - "

    First 1

    \n" - + "

    Then 2

    \n" - + "

    Then 1 again

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      f 2

      \n" - + "
    2. \n" - + "
    3. \n" - + "

      b

      \n" - + "
    4. \n" - + "
    \n" - + "
    \n"); + """ + First [^foo] + + Then [^bar] + + Then [^foo] again + + [^bar]: b + [^foo]: f + """, + """ +

    First 1

    +

    Then 2

    +

    Then 1 again

    +
    +
      +
    1. +

      f 2

      +
    2. +
    3. +

      b

      +
    4. +
    +
    + """); } @Test public void testDefinitionWithTwoParagraphs() { // With two paragraphs, the backref should be added to the second one assertRendering( - "Test [^foo]\n\n[^foo]: one\n \n two\n", - "

    Test 1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      one

      \n" - + "

      two

      \n" - + "
    2. \n" - + "
    \n" - + "
    \n"); + """ + Test [^foo] + + [^foo]: one + \s + two + """, + """ +

    Test 1

    +
    +
      +
    1. +

      one

      +

      two

      +
    2. +
    +
    + """); } @Test public void testDefinitionWithList() { assertRendering( - "Test [^foo]\n\n[^foo]:\n - one\n - two\n", - "

    Test 1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "
        \n" - + "
      • one
      • \n" - + "
      • two
      • \n" - + "
      \n" - + "
    2. \n" - + "
    \n" - + "
    \n"); + """ + Test [^foo] + + [^foo]: + - one + - two + """, + """ +

    Test 1

    +
    +
      +
    1. +
        +
      • one
      • +
      • two
      • +
      +
    2. +
    +
    + """); } // See docs on FootnoteHtmlNodeRenderer about nested footnotes. @@ -109,18 +148,25 @@ public void testDefinitionWithList() { @Test public void testNestedFootnotesSimple() { assertRendering( - "[^foo1]\n" + "\n" + "[^foo1]: one [^foo2]\n" + "[^foo2]: two\n", - "

    1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      one 2

      \n" - + "
    2. \n" - + "
    3. \n" - + "

      two

      \n" - + "
    4. \n" - + "
    \n" - + "
    \n"); + """ + [^foo1] + + [^foo1]: one [^foo2] + [^foo2]: two + """, + """ +

    1

    +
    +
      +
    1. +

      one 2

      +
    2. +
    3. +

      two

      +
    4. +
    +
    + """); } @Test @@ -129,149 +175,196 @@ public void testNestedFootnotesOrder() { // The reason is that the number is done based on all references in document order, // including references in definitions. So [^bar] from the first line is first. assertRendering( - "[^foo]: foo [^bar]\n" + "\n" + "[^foo]\n" + "\n" + "[^bar]: bar\n", - "

    1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      foo 2

      \n" - + "
    2. \n" - + "
    3. \n" - + "

      bar

      \n" - + "
    4. \n" - + "
    \n" - + "
    \n"); + """ + [^foo]: foo [^bar] + + [^foo] + + [^bar]: bar + """, + """ +

    1

    +
    +
      +
    1. +

      foo 2

      +
    2. +
    3. +

      bar

      +
    4. +
    +
    + """); } @Test public void testNestedFootnotesOrder2() { assertRendering( - "[^1]\n" - + "\n" - + "[^4]: four\n" - + "[^3]: three [^4]\n" - + "[^2]: two [^4]\n" - + "[^1]: one [^2][^3]\n", - "

    1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      one 23

      \n" - + "
    2. \n" - + "
    3. \n" - + "

      two 4

      \n" - + "
    4. \n" - + "
    5. \n" - + "

      three 4

      \n" - + "
    6. \n" - + "
    7. \n" - + "

      four 2

      \n" - + "
    8. \n" - + "
    \n" - + "
    \n"); + """ + [^1] + + [^4]: four + [^3]: three [^4] + [^2]: two [^4] + [^1]: one [^2][^3] + """, + """ +

    1

    +
    +
      +
    1. +

      one 23

      +
    2. +
    3. +

      two 4

      +
    4. +
    5. +

      three 4

      +
    6. +
    7. +

      four 2

      +
    8. +
    +
    + """); } @Test public void testNestedFootnotesCycle() { // Footnotes can contain cycles, lol. assertRendering( - "[^foo1]\n" + "\n" + "[^foo1]: one [^foo2]\n" + "[^foo2]: two [^foo1]\n", - "

    1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      one 2 2

      \n" - + "
    2. \n" - + "
    3. \n" - + "

      two 1

      \n" - + "
    4. \n" - + "
    \n" - + "
    \n"); + """ + [^foo1] + + [^foo1]: one [^foo2] + [^foo2]: two [^foo1] + """, + """ +

    1

    +
    +
      +
    1. +

      one 2 2

      +
    2. +
    3. +

      two 1

      +
    4. +
    +
    + """); } @Test public void testNestedFootnotesUnreferenced() { // This should not result in any footnotes, as baz itself isn't referenced. // But GitHub renders bar only, with a broken backref, because bar is referenced from foo. - assertRendering("[^foo]: foo[^bar]\n" + "[^bar]: bar\n", ""); + assertRendering( + """ + [^foo]: foo[^bar] + [^bar]: bar + """, + ""); // And here only 1 is rendered. assertRendering( - "[^1]\n" + "\n" + "[^1]: one\n" + "[^foo]: foo[^bar]\n" + "[^bar]: bar\n", - "

    1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      one

      \n" - + "
    2. \n" - + "
    \n" - + "
    \n"); + """ + [^1] + + [^1]: one + [^foo]: foo[^bar] + [^bar]: bar + """, + """ +

    1

    +
    +
      +
    1. +

      one

      +
    2. +
    +
    + """); } @Test public void testInlineFootnotes() { assertRenderingInline( "Test ^[inline *footnote*]", - "

    Test 1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      inline footnote

      \n" - + "
    2. \n" - + "
    \n" - + "
    \n"); + """ +

    Test 1

    +
    +
      +
    1. +

      inline footnote

      +
    2. +
    +
    + """); } @Test public void testInlineFootnotesNested() { assertRenderingInline( "Test ^[inline ^[nested]]", - "

    Test 1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      inline 2

      \n" - + "
    2. \n" - + "
    3. \n" - + "

      nested

      \n" - + "
    4. \n" - + "
    \n" - + "
    \n"); + """ +

    Test 1

    +
    +
      +
    1. +

      inline 2

      +
    2. +
    3. +

      nested

      +
    4. +
    +
    + """); } @Test public void testInlineFootnoteWithReference() { // This is a bit tricky because the IDs need to be unique. assertRenderingInline( - "Test ^[inline [^1]]\n" + "\n" + "[^1]: normal", - "

    Test 1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      inline 2

      \n" - + "
    2. \n" - + "
    3. \n" - + "

      normal

      \n" - + "
    4. \n" - + "
    \n" - + "
    \n"); + """ + Test ^[inline [^1]] + + [^1]: normal""", + """ +

    Test 1

    +
    +
      +
    1. +

      inline 2

      +
    2. +
    3. +

      normal

      +
    4. +
    +
    + """); } @Test public void testInlineFootnoteInsideDefinition() { assertRenderingInline( - "Test [^1]\n" + "\n" + "[^1]: Definition ^[inline]\n", - "

    Test 1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      Definition 2

      \n" - + "
    2. \n" - + "
    3. \n" - + "

      inline

      \n" - + "
    4. \n" - + "
    \n" - + "
    \n"); + """ + Test [^1] + + [^1]: Definition ^[inline] + """, + """ +

    Test 1

    +
    +
      +
    1. +

      Definition 2

      +
    2. +
    3. +

      inline

      +
    4. +
    +
    + """); } @Test @@ -279,24 +372,30 @@ public void testInlineFootnoteInsideDefinition2() { // Tricky because of the nested inline footnote which we want to visit after foo // (breadth-first). assertRenderingInline( - "Test [^1]\n" + "\n" + "[^1]: Definition ^[inline ^[nested]] ^[foo]\n", - "

    Test 1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      Definition 2 3

      \n" - + "
    2. \n" - + "
    3. \n" - + "

      inline 4

      \n" - + "
    4. \n" - + "
    5. \n" - + "

      foo

      \n" - + "
    6. \n" - + "
    7. \n" - + "

      nested

      \n" - + "
    8. \n" - + "
    \n" - + "
    \n"); + """ + Test [^1] + + [^1]: Definition ^[inline ^[nested]] ^[foo] + """, + """ +

    Test 1

    +
    +
      +
    1. +

      Definition 2 3

      +
    2. +
    3. +

      inline 4

      +
    4. +
    5. +

      foo

      +
    6. +
    7. +

      nested

      +
    8. +
    +
    + """); } @Test @@ -314,14 +413,16 @@ public void testRenderNodesDirectly() { doc.appendChild(def); var expected = - "

    Test 1

    \n" - + "
    \n" - + "
      \n" - + "
    1. \n" - + "

      note!

      \n" - + "
    2. \n" - + "
    \n" - + "
    \n"; + """ +

    Test 1

    +
    +
      +
    1. +

      note!

      +
    2. +
    +
    + """; Asserts.assertRendering("", expected, RENDERER.render(doc)); } diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java index 99c0e8d7a..bee8f7be2 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java @@ -59,10 +59,12 @@ public void customType() { assertThat(renderer.render(parser.parse("> [!INFO]\n> Custom alert"))) .isEqualTo( - "
    \n" - + "

    Information

    \n" - + "

    Custom alert

    \n" - + "
    \n"); + """ +
    +

    Information

    +

    Custom alert

    +
    + """); } @Test @@ -77,23 +79,33 @@ public void multipleCustomTypes() { var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); - assertThat( - renderer.render( - parser.parse( - "> [!INFO]\n> Info content\n\n> [!SUCCESS]\n> Success content\n\n> [!DANGER]\n> Danger content"))) + var s = + """ + > [!INFO] + > Info content + + > [!SUCCESS] + > Success content + + > [!DANGER] + > Danger content + """; + assertThat(renderer.render(parser.parse(s))) .isEqualTo( - "
    \n" - + "

    Information

    \n" - + "

    Info content

    \n" - + "
    \n" - + "
    \n" - + "

    Success!

    \n" - + "

    Success content

    \n" - + "
    \n" - + "
    \n" - + "

    Danger!

    \n" - + "

    Danger content

    \n" - + "
    \n"); + """ +
    +

    Information

    +

    Info content

    +
    +
    +

    Success!

    +

    Success content

    +
    +
    +

    Danger!

    +

    Danger content

    +
    + """); } @Test @@ -103,12 +115,19 @@ public void standardTypesWithCustomConfigured() { var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); - assertThat(renderer.render(parser.parse("> [!NOTE]\n> Standard type"))) + var s = + """ + > [!NOTE] + > Standard type + """; + assertThat(renderer.render(parser.parse(s))) .isEqualTo( - "
    \n" - + "

    Note

    \n" - + "

    Standard type

    \n" - + "
    \n"); + """ +
    +

    Note

    +

    Standard type

    +
    + """); } @Test @@ -118,12 +137,19 @@ public void overrideStandardTypeTitle() { var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); - assertThat(renderer.render(parser.parse("> [!NOTE]\n> Localized title"))) + var s = + """ + > [!NOTE] + > Localized title + """; + assertThat(renderer.render(parser.parse(s))) .isEqualTo( - "
    \n" - + "

    Nota

    \n" - + "

    Localized title

    \n" - + "
    \n"); + """ +
    +

    Nota

    +

    Localized title

    +
    + """); } // Custom type validation @@ -162,33 +188,61 @@ public void overwriteStandardTypes() { var parser = Parser.builder().extensions(Set.of(extension)).build(); var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); - assertThat(renderer.render(parser.parse("> [!NOTE]\n> Regular block quote"))) + var s = + """ + > [!NOTE] + > Regular block quote + """; + assertThat(renderer.render(parser.parse(s))) .isEqualTo( - "
    \n" - + "

    [!NOTE]\n" - + "Regular block quote

    \n" - + "
    \n"); - - assertThat(renderer.render(parser.parse("> [!TIP]\n> Regular block quote"))) + """ +
    +

    [!NOTE] + Regular block quote

    +
    + """); + + s = + """ + > [!TIP] + > Regular block quote + """; + assertThat(renderer.render(parser.parse(s))) .isEqualTo( - "
    \n" - + "

    [!TIP]\n" - + "Regular block quote

    \n" - + "
    \n"); - - assertThat(renderer.render(parser.parse("> [!IMPORTANT]\n> Alert"))) + """ +
    +

    [!TIP] + Regular block quote

    +
    + """); + + s = + """ + > [!IMPORTANT] + > Alert + """; + assertThat(renderer.render(parser.parse(s))) .isEqualTo( - "
    \n" - + "

    Important

    \n" - + "

    Alert

    \n" - + "
    \n"); - - assertThat(renderer.render(parser.parse("> [!BUG]\n> Alert"))) + """ +
    +

    Important

    +

    Alert

    +
    + """); + + s = + """ + > [!BUG] + > Alert + """; + assertThat(renderer.render(parser.parse(s))) .isEqualTo( - "
    \n" - + "

    Known Bug

    \n" - + "

    Alert

    \n" - + "
    \n"); + """ +
    +

    Known Bug

    +

    Alert

    +
    + """); } // Overwriting types validation @@ -222,130 +276,198 @@ public void overwriteTypesTitleMustNotBeEmpty() { @Test public void customTitle() { assertRenderingCustomTitles( - "> [!NOTE] Custom title\n> Note with a custom title", - "
    \n" - + "

    Custom title

    \n" - + "

    Note with a custom title

    \n" - + "
    \n"); + """ + > [!NOTE] Custom title + > Note with a custom title + """, + """ +
    +

    Custom title

    +

    Note with a custom title

    +
    + """); } @Test public void customTitleNoSpace() { assertRenderingCustomTitles( - "> [!NOTE]Custom title\n> Note with a custom title", - "
    \n" - + "

    Custom title

    \n" - + "

    Note with a custom title

    \n" - + "
    \n"); + """ + > [!NOTE]Custom title + > Note with a custom title + """, + """ +
    +

    Custom title

    +

    Note with a custom title

    +
    + """); } @Test public void customTitleExtraSpace() { assertRenderingCustomTitles( - "> [!NOTE] Custom title \n> Note with a custom title", - "
    \n" - + "

    Custom title

    \n" - + "

    Note with a custom title

    \n" - + "
    \n"); + """ + > [!NOTE] Custom title \s + > Note with a custom title + """, + """ +
    +

    Custom title

    +

    Note with a custom title

    +
    + """); } @Test public void customTitleNoHardLineBreak() { assertRenderingCustomTitles( - "> [!NOTE] Custom title\\\n> Note with a custom title", - "
    \n" - + "

    Custom title\\

    \n" - + "

    Note with a custom title

    \n" - + "
    \n"); + """ + > [!NOTE] Custom title\\ + > Note with a custom title + """, + """ +
    +

    Custom title\\

    +

    Note with a custom title

    +
    + """); } @Test public void customTitleWithComment() { assertRenderingCustomTitles( - "> [!NOTE] Custom title \n> Note with a custom title", - "
    \n" - + "

    Custom title

    \n" - + "

    Note with a custom title

    \n" - + "
    \n"); + """ + > [!NOTE] Custom title \s + > Note with a custom title + """, + """ +
    +

    Custom title

    +

    Note with a custom title

    +
    + """); } @Test public void customTitleWithInlineFormatting() { assertRenderingCustomTitles( - "> [!NOTE] Custom _title with **formatting**_\n> Note with a custom title", - "
    \n" - + "

    Custom title with formatting

    \n" - + "

    Note with a custom title

    \n" - + "
    \n"); + """ + > [!NOTE] Custom _title with **formatting**_ + > Note with a custom title + """, + """ +
    +

    Custom title with formatting

    +

    Note with a custom title

    +
    + """); } @Test public void customTitleWithLinkAndCode() { assertRenderingCustomTitles( - "> [!IMPORTANT] See [docs](https://example.com) or `run()`\n> Note with a custom title", - "
    \n" - + "

    See docs or run()

    \n" - + "

    Note with a custom title

    \n" - + "
    \n"); + """ + > [!IMPORTANT] See [docs](https://example.com) or `run()` + > Note with a custom title + """, + """ +
    +

    See docs or run()

    +

    Note with a custom title

    +
    + """); } @Test public void customTitleLeadingEmptyLines() { assertRenderingCustomTitles( - ">\n> \n> [!NOTE] Custom **title**\n> Note with a custom title", - "
    \n" - + "

    Custom title

    \n" - + "

    Note with a custom title

    \n" - + "
    \n"); + """ + > + > \s + > [!NOTE] Custom **title** + > Note with a custom title + """, + """ +
    +

    Custom title

    +

    Note with a custom title

    +
    + """); } @Test public void customTitleNoOverlappingInlines() { assertRenderingCustomTitles( - "> [!NOTE] Custom _title with **opening `delimiters\n> Note` with** closing delimiters_", - "
    \n" - + "

    Custom _title with **opening `delimiters

    \n" - + "

    Note` with** closing delimiters_

    \n" - + "
    \n"); + """ + > [!NOTE] Custom _title with **opening `delimiters + > Note` with** closing delimiters_ + """, + """ +
    +

    Custom _title with **opening `delimiters

    +

    Note` with** closing delimiters_

    +
    + """); } @Test public void customTitleIgnoresBlockContent() { assertRenderingCustomTitles( - "> [!NOTE] ## Custom title looks like an ATX heading\n>But it's not", - "
    \n" - + "

    ## Custom title looks like an ATX heading

    \n" - + "

    But it's not

    \n" - + "
    \n"); + """ + > [!NOTE] ## Custom title looks like an ATX heading + >But it's not + """, + """ +
    +

    ## Custom title looks like an ATX heading

    +

    But it's not

    +
    + """); } @Test public void customTitleNoBody() { // Alerts with no body are allowed. assertRenderingCustomTitles( - "> [!NOTE] Custom _title_\n> \n>\n>", - "
    \n" - + "

    Custom title

    \n" - + "
    \n"); + """ + > [!NOTE] Custom _title_ + > \s + > + > + """, + """ +
    +

    Custom title

    +
    + """); } @Test public void customTitleNoBodyNoSpace() { assertRenderingCustomTitles( - "> [!NOTE] Custom _title_", - "
    \n" - + "

    Custom title

    \n" - + "
    \n"); + """ + > [!NOTE] Custom _title_ + """, + """ +
    +

    Custom title

    +
    + """); } @Test public void onlyTrailingWhitespaceIsNotCustomTitle() { assertRenderingCustomTitles( - "> [!NOTE] \n> Body text", - "
    \n" - + "

    Note

    \n" - + "

    Body text

    \n" - + "
    \n"); + """ + > [!NOTE] \s + > Body text + """, + """ +
    +

    Note

    +

    Body text

    +
    + """); } // Lazy continuation @@ -353,21 +475,31 @@ public void onlyTrailingWhitespaceIsNotCustomTitle() { @Test public void noLazyContinuationAfterMarker() { assertRendering( - "> [!NOTE]\nBody text", - "
    \n" - + "

    Note

    \n" - + "
    \n" - + "

    Body text

    \n"); + """ + > [!NOTE] + Body text + """, + """ +
    +

    Note

    +
    +

    Body text

    + """); } @Test public void noLazyContinuationAfterTitle() { assertRenderingCustomTitles( - "> [!NOTE] Custom title\nBody text", - "
    \n" - + "

    Custom title

    \n" - + "
    \n" - + "

    Body text

    \n"); + """ + > [!NOTE] Custom title + Body text + """, + """ +
    +

    Custom title

    +
    +

    Body text

    + """); } // Alert markers take precedence over link reference definitions @@ -375,32 +507,53 @@ public void noLazyContinuationAfterTitle() { @Test public void alertTakesPrecedence() { assertRenderingCustomTitles( - "> [!NOTE]: https://example.com\n> Body text\n\n[!NOTE]", - "
    \n" - + "

    : https://example.com

    \n" - + "

    Body text

    \n" - + "
    \n" - + "

    [!NOTE]

    \n"); + """ + > [!NOTE]: https://example.com + > Body text + + [!NOTE] + """, + """ +
    +

    : https://example.com

    +

    Body text

    +
    +

    [!NOTE]

    + """); } @Test public void alertTakesPrecedenceBefore() { assertRenderingCustomTitles( - "> [!NOTE]\n> Body text\n\n[!NOTE]: https://example.com", - "
    \n" - + "

    Note

    \n" - + "

    Body text

    \n" - + "
    \n"); + """ + > [!NOTE] + > Body text + + [!NOTE]: https://example.com + """, + """ +
    +

    Note

    +

    Body text

    +
    + """); } @Test public void alertTakesPrecedenceAfter() { assertRenderingCustomTitles( - "[!NOTE]: https://example.com\n\n> [!NOTE]\n> Body text", - "
    \n" - + "

    Note

    \n" - + "

    Body text

    \n" - + "
    \n"); + """ + [!NOTE]: https://example.com + + > [!NOTE] + > Body text + """, + """ +
    +

    Note

    +

    Body text

    +
    + """); } // Nested alerts @@ -408,37 +561,55 @@ public void alertTakesPrecedenceAfter() { @Test public void noNestedAlertsByDefault() { assertRendering( - "> [!TIP]\n> Body\n\n- > [!TIP]\n > Nested body", - "
    \n" - + "

    Tip

    \n" - + "

    Body

    \n" - + "
    \n" - + "
      \n" - + "
    • \n" - + "
      \n" - + "

      [!TIP]\n" - + "Nested body

      \n" - + "
      \n" - + "
    • \n" - + "
    \n"); + """ + > [!TIP] + > Body + + - > [!TIP] + > Nested body + """, + """ +
    +

    Tip

    +

    Body

    +
    +
      +
    • +
      +

      [!TIP] + Nested body

      +
      +
    • +
    + """); } @Test public void noNestedAlertsByDefaultLeadingEmptyLines() { assertRendering( - ">\n> \n> [!TIP]\n> Body\n\n- > [!TIP]\n > Nested body", - "
    \n" - + "

    Tip

    \n" - + "

    Body

    \n" - + "
    \n" - + "
      \n" - + "
    • \n" - + "
      \n" - + "

      [!TIP]\n" - + "Nested body

      \n" - + "
      \n" - + "
    • \n" - + "
    \n"); + """ + > + > \s + > [!TIP] + > Body + + - > [!TIP] + > Nested body + """, + """ +
    +

    Tip

    +

    Body

    +
    +
      +
    • +
      +

      [!TIP] + Nested body

      +
      +
    • +
    + """); } @Test @@ -448,50 +619,49 @@ public void nestedAlerts() { var renderer = HtmlRenderer.builder().extensions(Set.of(extension)).build(); var source = - String.join( - "\n", - "> [!TIP]", - "> Tip body", - "> > [!IMPORTANT]", - "> > Important body", - "", - "- > [!NOTE]", - " > Nested body", - " >", - " > 1. Ordered list body", - " >", - " > > [!CAUTION]", - " > >", - " > > Deeply nested body"); + """ + > [!TIP] + > Tip body + > > [!IMPORTANT] + > > Important body + + - > [!NOTE] + > Nested body + > + > 1. Ordered list body + > + > > [!CAUTION] + > > + > > Deeply nested body + """; var expected = - String.join( - "\n", - "
    ", - "

    Tip

    ", - "

    Tip body

    ", - "
    ", - "

    Important

    ", - "

    Important body

    ", - "
    ", - "
    ", - "
      ", - "
    • ", - "
      ", - "

      Note

      ", - "

      Nested body

      ", - "
        ", - "
      1. ", - "

        Ordered list body

        ", - "
        ", - "

        Caution

        ", - "

        Deeply nested body

        ", - "
        ", - "
      2. ", - "
      ", - "
      ", - "
    • ", - "
    \n"); - + """ +
    +

    Tip

    +

    Tip body

    +
    +

    Important

    +

    Important body

    +
    +
    +
      +
    • +
      +

      Note

      +

      Nested body

      +
        +
      1. +

        Ordered list body

        +
        +

        Caution

        +

        Deeply nested body

        +
        +
      2. +
      +
      +
    • +
    + """; assertThat(renderer.render(parser.parse(source))).isEqualTo(expected); } @@ -499,7 +669,12 @@ public void nestedAlerts() { @Test public void alertParsedAsAlertNode() { - var document = PARSER.parse("> [!NOTE]\n> This is a note"); + var document = + PARSER.parse( + """ + > [!NOTE] + > This is a note + """); var firstChild = document.getFirstChild(); assertThat(firstChild).isInstanceOf(Alert.class); var alert = (Alert) firstChild; @@ -522,7 +697,11 @@ public void customTypeParsedAsAlertNode() { @Test public void titleSourcePositionPreserved() { - var source = "> [!NOTE] Custom title\n> Body text"; + var source = + """ + > [!NOTE] Custom title + > Body text + """; var document = PARSER_CUSTOM_TITLES.parse(source); var alert = (Alert) document.getFirstChild(); var title = (AlertTitle) alert.getFirstChild(); @@ -533,7 +712,15 @@ public void titleSourcePositionPreserved() { @Test public void titleSourcePositionPreservedBetweenBlocks() { - var source = "- List\n\n> [!NOTE] Custom title\n> Body text\n\nPlain paragraph"; + var source = + """ + - List + + > [!NOTE] Custom title + > Body text + + Plain paragraph + """; var document = PARSER_CUSTOM_TITLES.parse(source); var alert = (Alert) document.getFirstChild().getNext(); var title = (AlertTitle) alert.getFirstChild(); @@ -544,7 +731,11 @@ public void titleSourcePositionPreservedBetweenBlocks() { @Test public void titleSourcePositionWithLeadingAndTrailingSpaces() { - var source = "> [!NOTE] Custom title \n> Body text"; + var source = + """ + > [!NOTE] Custom title \s + > Body text + """; var document = PARSER_CUSTOM_TITLES.parse(source); var alert = (Alert) document.getFirstChild(); var title = (AlertTitle) alert.getFirstChild(); @@ -555,7 +746,11 @@ public void titleSourcePositionWithLeadingAndTrailingSpaces() { @Test public void titleWithInlineFormattingSourcePosition() { - var source = "> [!NOTE] Custom _title_\n> Body text"; + var source = + """ + > [!NOTE] Custom _title_ + > Body text + """; var document = PARSER_CUSTOM_TITLES.parse(source); var alert = (Alert) document.getFirstChild(); var title = (AlertTitle) alert.getFirstChild(); @@ -583,7 +778,11 @@ public void titleWithInlineFormattingSourcePosition() { @Test public void titleWithNestedInlineFormattingSourcePosition() { - var source = "> [!NOTE] Text with **bold _and italic_**\n> Body text"; + var source = + """ + > [!NOTE] Text with **bold _and italic_** + > Body text + """; var document = PARSER_CUSTOM_TITLES.parse(source); var alert = (Alert) document.getFirstChild(); var title = (AlertTitle) alert.getFirstChild(); diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java index 26b348016..15ef46c9c 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java @@ -25,17 +25,24 @@ private static void standardTypesExample() { var renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); var markdown = - "# GFM Alerts Demo\n\n" - + "> [!NOTE]\n" - + "> Highlights information that users should take into account.\n\n" - + "> [!TIP]\n" - + "> Helpful advice for doing things better.\n\n" - + "> [!IMPORTANT]\n" - + "> Key information users need to know.\n\n" - + "> [!WARNING]\n" - + "> Urgent info that needs immediate attention.\n\n" - + "> [!CAUTION]\n" - + "> Advises about risks or negative outcomes.\n"; + """ + # GFM Alerts Demo + + > [!NOTE] + > Highlights information that users should take into account. + + > [!TIP] + > Helpful advice for doing things better. + + > [!IMPORTANT] + > Key information users need to know. + + > [!WARNING] + > Urgent info that needs immediate attention. + + > [!CAUTION] + > Advises about risks or negative outcomes. + """; var html = renderer.render(parser.parse(markdown)); @@ -56,13 +63,18 @@ private static void customTypesExample() { var renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); var markdown = - "# Custom Alert Types\n\n" - + "> [!NOTE]\n" - + "> Useful information that users should know.\n\n" - + "> [!TIP]\n" - + "> Helpful advice for doing things better.\n\n" - + "> [!BUG]\n" - + "> This feature has a known issue with large files (see #42).\n"; + """ + # Custom Alert Types + + > [!NOTE] + > Useful information that users should know. + + > [!TIP] + > Helpful advice for doing things better. + + > [!BUG] + > This feature has a known issue with large files (see #42). + """; var html = renderer.render(parser.parse(markdown)); diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java index 5ddee95b6..7621660f9 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -30,238 +30,405 @@ public void mustHaveHeaderAndSeparator() { @Test public void separatorMustBeOneOrMore() { assertRendering( - "Abc|Def\n-|-", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    \n"); - assertRendering( - "Abc|Def\n--|--", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    \n"); + """ + Abc|Def + -|- + """, + """ + + + + + + + +
    AbcDef
    + """); + assertRendering( + """ + Abc|Def + --|-- + """, + """ + + + + + + + +
    AbcDef
    + """); } @Test public void separatorMustNotContainInvalidChars() { - assertRendering("Abc|Def\n |-a-|---", "

    Abc|Def\n|-a-|---

    \n"); - assertRendering("Abc|Def\n |:--a|---", "

    Abc|Def\n|:--a|---

    \n"); - assertRendering("Abc|Def\n |:--a--:|---", "

    Abc|Def\n|:--a--:|---

    \n"); + assertRendering( + """ + Abc|Def + |-a-|--- + """, + """ +

    Abc|Def + |-a-|---

    + """); + assertRendering( + """ + Abc|Def + |:--a|--- + """, + """ +

    Abc|Def + |:--a|---

    + """); + assertRendering( + """ + Abc|Def + |:--a--:|--- + """, + """ +

    Abc|Def + |:--a--:|---

    + """); } @Test public void separatorCanHaveLeadingSpaceThenPipe() { assertRendering( - "Abc|Def\n |---|---", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    \n"); + """ + Abc|Def + |---|--- + """, + """ + + + + + + + +
    AbcDef
    + """); } @Test public void separatorCanNotHaveAdjacentPipes() { - assertRendering("Abc|Def\n---||---", "

    Abc|Def\n---||---

    \n"); + assertRendering( + """ + Abc|Def + ---||--- + """, + """ +

    Abc|Def + ---||---

    + """); } @Test public void separatorNeedsPipes() { - assertRendering("Abc|Def\n|--- ---", "

    Abc|Def\n|--- ---

    \n"); + assertRendering( + """ + Abc|Def + |--- --- + """, + """ +

    Abc|Def + |--- ---

    + """); } @Test public void oneHeadNoBody() { assertRendering( - "Abc|Def\n---|---", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    \n"); + """ + Abc|Def + ---|--- + """, + """ + + + + + + + +
    AbcDef
    + """); } @Test public void oneColumnOneHeadNoBody() { String expected = - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    Abc
    \n"; - assertRendering("|Abc\n|---\n", expected); - assertRendering("|Abc|\n|---|\n", expected); - assertRendering("Abc|\n---|\n", expected); + """ + + + + + + +
    Abc
    + """; + assertRendering( + """ + |Abc + |--- + """, + expected); + assertRendering( + """ + |Abc| + |---| + """, + expected); + assertRendering( + """ + Abc| + ---| + """, + expected); // Pipe required on separator - assertRendering("|Abc\n---\n", "

    |Abc

    \n"); + assertRendering( + """ + |Abc + --- + """, + """ +

    |Abc

    + """); // Pipe required on head - assertRendering("Abc\n|---\n", "

    Abc\n|---

    \n"); + assertRendering( + """ + Abc + |--- + """, + """ +

    Abc + |---

    + """); } @Test public void oneColumnOneHeadOneBody() { String expected = - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    Abc
    1
    \n"; - assertRendering("|Abc\n|---\n|1", expected); - assertRendering("|Abc|\n|---|\n|1|", expected); - assertRendering("Abc|\n---|\n1|", expected); + """ + + + + + + + + + + + +
    Abc
    1
    + """; + assertRendering( + """ + |Abc + |--- + |1 + """, + expected); + assertRendering( + """ + |Abc| + |---| + |1| + """, + expected); + assertRendering( + """ + Abc| + ---| + 1| + """, + expected); // Pipe required on separator - assertRendering("|Abc\n---\n|1", "

    |Abc

    \n

    |1

    \n"); + assertRendering( + """ + |Abc + --- + |1 + """, + """ +

    |Abc

    +

    |1

    + """); } @Test public void oneHeadOneBody() { assertRendering( - "Abc|Def\n---|---\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + Abc|Def + ---|--- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test public void spaceBeforeSeparator() { assertRendering( - " |Abc|Def|\n |---|---|\n |1|2|", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + |Abc|Def| + |---|---| + |1|2| + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test public void separatorMustNotHaveLessPartsThanHead() { - assertRendering("Abc|Def|Ghi\n---|---\n1|2|3", "

    Abc|Def|Ghi\n---|---\n1|2|3

    \n"); + assertRendering( + """ + Abc|Def|Ghi + ---|--- + 1|2|3 + """, + """ +

    Abc|Def|Ghi + ---|--- + 1|2|3

    + """); } @Test public void padding() { assertRendering( - " Abc | Def \n --- | --- \n 1 | 2 ", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + Abc | Def\s + --- | ---\s + 1 | 2\s + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test public void paddingWithCodeBlockIndentation() { assertRendering( - "Abc|Def\n---|---\n 1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + Abc|Def + ---|--- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test public void pipesOnOutside() { assertRendering( - "|Abc|Def|\n|---|---|\n|1|2|", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + |Abc|Def| + |---|---| + |1|2| + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test public void pipesOnOutsideWhitespaceAfterHeader() { assertRendering( - "|Abc|Def| \n|---|---|\n|1|2|", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + |Abc|Def|\s + |---|---| + |1|2| + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test @@ -269,63 +436,81 @@ public void pipesOnOutsideZeroLengthHeaders() { // This is literally what someone has done IRL - it helped to expose // an issue with parsing the last header cell correctly assertRendering( - "||center header||\n" + "-|-------------|-\n" + "1| 2 |3", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    center header
    123
    \n"); + """ + ||center header|| + -|-------------|- + 1| 2 |3 + """, + """ + + + + + + + + + + + + + + + +
    center header
    123
    + """); } @Test public void inlineElements() { assertRendering( - "*Abc*|Def\n---|---\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + *Abc*|Def + ---|--- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test public void escapedPipe() { assertRendering( - "Abc|Def\n---|---\n1\\|2|20", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    1|220
    \n"); + """ + Abc|Def + ---|--- + 1\\|2|20 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    1|220
    + """); } @Test @@ -334,21 +519,27 @@ public void escapedBackslash() { // followed by a pipe (so two cells). Instead, the `\|` is parsed as an escaped pipe first, // so just a single cell. The inline parser then gets `1\|2` which renders as `1|2`. assertRendering( - "Abc|Def\n---|---\n1\\\\|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    1|2
    \n"); + """ + Abc|Def + ---|--- + 1\\\\|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    1|2
    + """); } @Test @@ -357,421 +548,580 @@ public void escapedOther() { // table, otherwise inline parsing is wrong. So we have to be careful where we do/don't // consume the backslash. assertRendering( - "Abc|Def\n---|---\n1|\\`not code`", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    1`not code`
    \n"); + """ + Abc|Def + ---|--- + 1|\\`not code` + """, + """ + + + + + + + + + + + + + +
    AbcDef
    1`not code`
    + """); } @Test public void backslashAtEnd() { assertRendering( - "Abc|Def\n---|---\n1|2\\", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12\\
    \n"); + """ + Abc|Def + ---|--- + 1|2\\ + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12\\
    + """); } @Test public void alignLeft() { assertRendering( - "Abc|Def\n:-|-\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); - assertRendering( - "Abc|Def\n:-|-\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); - assertRendering( - "Abc|Def\n:---|---\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + Abc|Def + :-|- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); + assertRendering( + """ + Abc|Def + :-|- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); + assertRendering( + """ + Abc|Def + :---|--- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test public void alignRight() { assertRendering( - "Abc|Def\n-:|-\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); - assertRendering( - "Abc|Def\n--:|--\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); - assertRendering( - "Abc|Def\n---:|---\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + Abc|Def + -:|- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); + assertRendering( + """ + Abc|Def + --:|-- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); + assertRendering( + """ + Abc|Def + ---:|--- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test public void alignCenter() { assertRendering( - "Abc|Def\n:-:|-\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); - assertRendering( - "Abc|Def\n:--:|--\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); - assertRendering( - "Abc|Def\n:---:|---\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + Abc|Def + :-:|- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); + assertRendering( + """ + Abc|Def + :--:|-- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); + assertRendering( + """ + Abc|Def + :---:|--- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test public void alignCenterSecond() { assertRendering( - "Abc|Def\n---|:---:\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + Abc|Def + ---|:---: + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test public void alignLeftWithSpaces() { assertRendering( - "Abc|Def\n :--- |---\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + Abc|Def + :--- |--- + 1|2 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test public void alignmentMarkerMustBeNextToDashes() { - assertRendering("Abc|Def\n: ---|---", "

    Abc|Def\n: ---|---

    \n"); - assertRendering("Abc|Def\n--- :|---", "

    Abc|Def\n--- :|---

    \n"); - assertRendering("Abc|Def\n---|: ---", "

    Abc|Def\n---|: ---

    \n"); - assertRendering("Abc|Def\n---|--- :", "

    Abc|Def\n---|--- :

    \n"); + assertRendering( + """ + Abc|Def + : ---|--- + """, + """ +

    Abc|Def + : ---|---

    + """); + assertRendering( + """ + Abc|Def + --- :|--- + """, + """ +

    Abc|Def + --- :|---

    + """); + assertRendering( + """ + Abc|Def + ---|: --- + """, + """ +

    Abc|Def + ---|: ---

    + """); + assertRendering( + """ + Abc|Def + ---|--- : + """, + """ +

    Abc|Def + ---|--- :

    + """); } @Test public void bodyCanNotHaveMoreColumnsThanHead() { assertRendering( - "Abc|Def\n---|---\n1|2|3", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + Abc|Def + ---|--- + 1|2|3 + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test public void bodyWithFewerColumnsThanHeadResultsInEmptyCells() { assertRendering( - "Abc|Def|Ghi\n---|---|---\n1|2", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDefGhi
    12
    \n"); + """ + Abc|Def|Ghi + ---|---|--- + 1|2 + """, + """ + + + + + + + + + + + + + + + +
    AbcDefGhi
    12
    + """); } @Test public void insideBlockQuote() { assertRendering( - "> Abc|Def\n> ---|---\n> 1|2", - "
    \n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n" - + "
    \n"); + """ + > Abc|Def + > ---|--- + > 1|2 + """, + """ +
    + + + + + + + + + + + + + +
    AbcDef
    12
    +
    + """); } @Test public void tableWithLazyContinuationLine() { assertRendering( - "Abc|Def\n---|---\n1|2\nlazy", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    lazy
    \n"); + """ + Abc|Def + ---|--- + 1|2 + lazy + """, + """ + + + + + + + + + + + + + + + + + +
    AbcDef
    12
    lazy
    + """); } @Test public void issue142() { assertRendering( - "||Alveolar|Bilabial\n" - + "|:--|:-:|:-:\n" - + "|**Plosive**|t, d|b\n" - + "|**Tap**|ɾ|", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AlveolarBilabial
    Plosivet, db
    Tapɾ
    \n"); + """ + ||Alveolar|Bilabial + |:--|:-:|:-: + |**Plosive**|t, d|b + |**Tap**|ɾ|""", + """ + + + + + + + + + + + + + + + + + + + + +
    AlveolarBilabial
    Plosivet, db
    Tapɾ
    + """); } @Test public void danglingPipe() { assertRendering( - "Abc|Def\n" + "---|---\n" + "1|2\n" + "|", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n" - + "

    |

    \n"); - - assertRendering( - "Abc|Def\n" + "---|---\n" + "1|2\n" + " | ", - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n" - + "

    |

    \n"); + """ + Abc|Def + ---|--- + 1|2 + | + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    +

    |

    + """); + + assertRendering( + """ + Abc|Def + ---|--- + 1|2 + | \s + """, + """ + + + + + + + + + + + + + +
    AbcDef
    12
    +

    |

    + """); } @Test public void interruptsParagraph() { assertRendering( - "text\n" + "|a |\n" + "|---|\n" + "|b |", - "

    text

    \n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    a
    b
    \n"); + """ + text + |a | + |---| + |b | + """, + """ +

    text

    + + + + + + + + + + + +
    a
    b
    + """); } @Test @@ -799,20 +1149,22 @@ public void attributeProviderIsApplied() { String rendered = renderer.render(PARSER.parse("Abc|Def\n---|---\n1|2")); assertThat(rendered) .isEqualTo( - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test @@ -832,20 +1184,22 @@ public void columnWidthIsRecorded() { String rendered = renderer.render(PARSER.parse("Abc|Def\n-----|---\n1|2")); assertThat(rendered) .isEqualTo( - "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "\n" - + "
    AbcDef
    12
    \n"); + """ + + + + + + + + + + + + + +
    AbcDef
    12
    + """); } @Test @@ -937,7 +1291,15 @@ public void sourceSpansWhenInterrupting() { .extensions(EXTENSIONS) .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) .build(); - var document = parser.parse("a\n" + "bc\n" + "|de|\n" + "|---|\n" + "|fg|"); + var document = + parser.parse( + """ + a + bc + |de| + |---| + |fg| + """); var paragraph = (Paragraph) document.getFirstChild(); var text = (Text) paragraph.getFirstChild(); diff --git a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java index 95ee2d7eb..07b281bf1 100644 --- a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java @@ -16,55 +16,96 @@ public class HeadingAnchorTest extends RenderingTestCase { @Test public void baseCaseSingleHeader() { - assertRendering("# Heading here\n", "

    Heading here

    \n"); + assertRendering( + """ + # Heading here + """, + """ +

    Heading here

    + """); } @Test public void singleHeaderWithCodeBlock() { assertRendering( - "Hi there\n# Heading `here`\n", - "

    Hi there

    \n

    Heading here

    \n"); + """ + Hi there + # Heading `here` + """, + """ +

    Hi there

    +

    Heading here

    + """); } @Test public void duplicateHeadersMakeUniqueIds() { assertRendering( - "# Heading here\n# Heading here", - "

    Heading here

    \n

    Heading here

    \n"); + """ + # Heading here + # Heading here + """, + """ +

    Heading here

    +

    Heading here

    + """); } @Test public void testSupplementalDiacriticalMarks() { - assertRendering("# a\u1DC0", "

    a\u1DC0

    \n"); + assertRendering( + "# a\u1DC0", + """ +

    a\u1DC0

    + """); } @Test public void testUndertieUnicodeDisplayed() { - assertRendering("# undertie \u203F", "

    undertie \u203F

    \n"); + assertRendering( + "# undertie \u203F", + """ +

    undertie \u203F

    + """); } @Test public void testExplicitHeaderCollision() { assertRendering( - "# Header\n# Header\n# Header-1", - "

    Header

    \n" - + "

    Header

    \n" - + "

    Header-1

    \n"); + """ + # Header + # Header + # Header-1 + """, + """ +

    Header

    +

    Header

    +

    Header-1

    + """); } @Test public void testCaseIsIgnoredWhenComparingIds() { assertRendering( - "# HEADING here\n" + "# heading here", - "

    HEADING here

    \n" - + "

    heading here

    \n"); + """ + # HEADING here + # heading here + """, + """ +

    HEADING here

    +

    heading here

    + """); } @Test public void testNestedBlocks() { assertRendering( - "## `h` `e` **l** *l* o", - "

    h e l l o

    \n"); + """ + ## `h` `e` **l** *l* o + """, + """ +

    h e l l o

    + """); } @Test @@ -77,25 +118,45 @@ public void boldEmphasisCharacters() { @Test public void testStrongEmphasis() { assertRendering( - "# _**Hi there**_", - "

    Hi there

    \n"); + """ + # _**Hi there**_ + """, + """ +

    Hi there

    + """); } @Test public void testMultipleSpacesKept() { - assertRendering("# Hi There", "

    Hi There

    \n"); + assertRendering( + """ + # Hi There + """, + """ +

    Hi There

    + """); } @Test public void testNonAsciiCharacterHeading() { - assertRendering("# bär", "

    bär

    \n"); + assertRendering( + """ + # bär + """, + """ +

    bär

    + """); } @Test public void testCombiningDiaeresis() { assertRendering( - "# Product\u036D\u036B", - "

    Product\u036D\u036B

    \n"); + """ + # Product\u036D\u036B + """, + """ +

    Product\u036D\u036B

    + """); } @Override diff --git a/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java index dd9290b6e..ff74c0d66 100644 --- a/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java +++ b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java @@ -20,110 +20,253 @@ public class TaskListItemsTest extends RenderingTestCase { @Test public void baseCase() { assertRendering( - "- [x] this is *done*\n", - "
      \n
    • " + HTML_CHECKED + " this is done
    • \n
    \n"); + """ + - [x] this is *done* + """, + """ +
      +
    • %s this is done
    • +
    + """ + .formatted(HTML_CHECKED)); assertRendering( - "- [ ] do this\n", "
      \n
    • " + HTML_UNCHECKED + " do this
    • \n
    \n"); + """ + - [ ] do this + """, + """ +
      +
    • %s do this
    • +
    + """ + .formatted(HTML_UNCHECKED)); assertRendering( - "- [x] foo\n" + " - [ ] bar\n" + " - [x] baz\n" + "- [ ] bim", - "
      \n" - + "
    • " - + HTML_CHECKED - + " foo\n" - + "
        \n" - + "
      • " - + HTML_UNCHECKED - + " bar
      • \n" - + "
      • " - + HTML_CHECKED - + " baz
      • \n" - + "
      \n" - + "
    • \n" - + "
    • " - + HTML_UNCHECKED - + " bim
    • \n" - + "
    \n"); + """ + - [x] foo + - [ ] bar + - [x] baz + - [ ] bim + """, + """ +
      +
    • %s foo +
        +
      • %s bar
      • +
      • %s baz
      • +
      +
    • +
    • %s bim
    • +
    + """ + .formatted(HTML_CHECKED, HTML_UNCHECKED, HTML_CHECKED, HTML_UNCHECKED)); assertRendering( - "* [ ] do this\n* [ ] and this", - "
      \n
    • " - + HTML_UNCHECKED - + " do this
    • \n
    • " - + HTML_UNCHECKED - + " and this
    • \n
    \n"); + """ + * [ ] do this + * [ ] and this + """, + """ +
      +
    • %s do this
    • +
    • %s and this
    • +
    + """ + .formatted(HTML_UNCHECKED, HTML_UNCHECKED)); assertRendering( - "+ [x] one\n" + " - [ ] two\n" + " * [x] three\n", - "
      \n" - + "
    • " - + HTML_CHECKED - + " one\n" - + "
        \n" - + "
      • " - + HTML_UNCHECKED - + " two\n" - + "
          \n" - + "
        • " - + HTML_CHECKED - + " three
        • \n" - + "
        \n" - + "
      • \n" - + "
      \n" - + "
    • \n" - + "
    \n"); + """ + + [x] one + - [ ] two + * [x] three + """, + """ +
      +
    • %s one +
        +
      • %s two +
          +
        • %s three
        • +
        +
      • +
      +
    • +
    + """ + .formatted(HTML_CHECKED, HTML_UNCHECKED, HTML_CHECKED)); assertRendering( - "TODO list\n" - + "---------\n" - + "- [ ] first task\n" - + "- [x] second task\n" - + "- [ ] third task\n\n" - + "Let me know when you are finished", - "

    TODO list

    \n" - + "
      \n" - + "
    • " - + HTML_UNCHECKED - + " first task
    • \n" - + "
    • " - + HTML_CHECKED - + " second task
    • \n" - + "
    • " - + HTML_UNCHECKED - + " third task
    • \n" - + "
    \n" - + "

    Let me know when you are finished

    \n"); + """ + TODO list + --------- + - [ ] first task + - [x] second task + - [ ] third task + + Let me know when you are finished + """, + """ +

    TODO list

    +
      +
    • %s first task
    • +
    • %s second task
    • +
    • %s third task
    • +
    +

    Let me know when you are finished

    + """ + .formatted(HTML_UNCHECKED, HTML_CHECKED, HTML_UNCHECKED)); } @Test public void notListItem() { - assertRendering("[x] this is not a task\n", "

    [x] this is not a task

    \n"); assertRendering( - " [ ] this is not a task either\n", "

    [ ] this is not a task either

    \n"); + """ + [x] this is not a task + """, + """ +

    [x] this is not a task

    + """); + assertRendering( + """ + [ ] this is not a task either + """, + """ +

    [ ] this is not a task either

    + """); } @Test public void notValidTaskFormat() { - assertRendering("- [x]no space\n", "
      \n
    • [x]no space
    • \n
    \n"); - assertRendering( - "- [O] is not a _task_\n", "
      \n
    • [O] is not a task
    • \n
    \n"); - assertRendering("* [] neither is this\n", "
      \n
    • [] neither is this
    • \n
    \n"); - assertRendering( - "* [ ] nor this\n" + "* [XX] nor this\n", - "
      \n
    • [ ] nor this
    • \n
    • [XX] nor this
    • \n
    \n"); - assertRendering("+ [x]] is not a task\n", "
      \n
    • [x]] is not a task
    • \n
    \n"); - assertRendering("- [x isn't\n", "
      \n
    • [x isn't
    • \n
    \n"); - assertRendering("- [[x is not\n", "
      \n
    • [[x is not
    • \n
    \n"); - assertRendering("- x] nope\n", "
      \n
    • x] nope
    • \n
    \n"); - assertRendering("- x]] no way\n", "
      \n
    • x]] no way
    • \n
    \n"); - assertRendering("+ (x) sorry no\n", "
      \n
    • (x) sorry no
    • \n
    \n"); - assertRendering("+ {x} sorry not sorry\n", "
      \n
    • {x} sorry not sorry
    • \n
    \n"); - assertRendering("+ [[x]] nooo\n", "
      \n
    • [[x]] nooo
    • \n
    \n"); - assertRendering( - "+ text before [x] is not a task\n", - "
      \n
    • text before [x] is not a task
    • \n
    \n"); - assertRendering("* [x] \n* [ ] \n", "
      \n
    • [x]
    • \n
    • [ ]
    • \n
    \n"); + assertRendering( + """ + - [x]no space + """, + """ +
      +
    • [x]no space
    • +
    + """); + assertRendering( + """ + - [O] is not a _task_ + """, + """ +
      +
    • [O] is not a task
    • +
    + """); + assertRendering( + """ + * [] neither is this + """, + """ +
      +
    • [] neither is this
    • +
    + """); + assertRendering( + """ + * [ ] nor this + * [XX] nor this + """, + """ +
      +
    • [ ] nor this
    • +
    • [XX] nor this
    • +
    + """); + assertRendering( + """ + + [x]] is not a task + """, + """ +
      +
    • [x]] is not a task
    • +
    + """); + assertRendering( + """ + - [x isn't + """, + """ +
      +
    • [x isn't
    • +
    + """); + assertRendering( + """ + - [[x is not + """, + """ +
      +
    • [[x is not
    • +
    + """); + assertRendering( + """ + - x] nope + """, + """ +
      +
    • x] nope
    • +
    + """); + assertRendering( + """ + - x]] no way + """, + """ +
      +
    • x]] no way
    • +
    + """); + assertRendering( + """ + + (x) sorry no + """, + """ +
      +
    • (x) sorry no
    • +
    + """); + assertRendering( + """ + + {x} sorry not sorry + """, + """ +
      +
    • {x} sorry not sorry
    • +
    + """); + assertRendering( + """ + + [[x]] nooo + """, + """ +
      +
    • [[x]] nooo
    • +
    + """); + assertRendering( + """ + + text before [x] is not a task + """, + """ +
      +
    • text before [x] is not a task
    • +
    + """); + assertRendering( + """ + * [x] \s + * [ ] \s + """, + """ +
      +
    • [x]
    • +
    • [ ]
    • +
    + """); } @Override diff --git a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterDataTest.java b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterDataTest.java index 6ead2c48b..6493ba716 100644 --- a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterDataTest.java +++ b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterDataTest.java @@ -20,7 +20,13 @@ Set getExtensions() { @Test public void simpleValue() { - final String input = "---" + "\nhello: world" + "\n..." + "\n" + "\ngreat"; + final String input = + """ + --- + hello: world + ... + + great"""; final String rendered = "

    great

    \n"; Map> data = getFrontMatterData(input); @@ -35,7 +41,13 @@ public void simpleValue() { @Test public void emptyValue() { - final String input = "---" + "\nkey:" + "\n---" + "\n" + "\ngreat"; + final String input = + """ + --- + key: + --- + + great"""; final String rendered = "

    great

    \n"; Map> data = getFrontMatterData(input); @@ -50,7 +62,15 @@ public void emptyValue() { @Test public void listValues() { final String input = - "---" + "\nlist:" + "\n - value1" + "\n - value2" + "\n..." + "\n" + "\ngreat"; + """ + --- + list: + - value1 + - value2 + ... + + great + """; final String rendered = "

    great

    \n"; Map> data = getFrontMatterData(input); @@ -67,13 +87,15 @@ public void listValues() { @Test public void literalValue1() { final String input = - "---" - + "\nliteral: |" - + "\n hello markdown!" - + "\n literal thing..." - + "\n---" - + "\n" - + "\ngreat"; + """ + --- + literal: | + hello markdown! + literal thing... + --- + + great + """; final String rendered = "

    great

    \n"; Map> data = getFrontMatterData(input); @@ -89,7 +111,14 @@ public void literalValue1() { @Test public void literalValue2() { final String input = - "---" + "\nliteral: |" + "\n - hello markdown!" + "\n---" + "\n" + "\ngreat"; + """ + --- + literal: | + - hello markdown! + --- + + great + """; final String rendered = "

    great

    \n"; Map> data = getFrontMatterData(input); @@ -105,17 +134,19 @@ public void literalValue2() { @Test public void complexValues() { final String input = - "---" - + "\nsimple: value" - + "\nliteral: |" - + "\n hello markdown!" - + "\n" - + "\n literal literal" - + "\nlist:" - + "\n - value1" - + "\n - value2" - + "\n---" - + "\ngreat"; + """ + --- + simple: value + literal: | + hello markdown! + + literal literal + list: + - value1 + - value2 + --- + great + """; final String rendered = "

    great

    \n"; Map> data = getFrontMatterData(input); @@ -140,7 +171,12 @@ public void complexValues() { @Test public void empty() { - final String input = "---\n" + "---\n" + "test"; + final String input = + """ + --- + --- + test + """; final String rendered = "

    test

    \n"; Map> data = getFrontMatterData(input); @@ -153,9 +189,20 @@ public void empty() { @Test public void yamlInParagraph() { final String input = - "# hello\n" + "\nhello markdown world!" + "\n---" + "\nhello: world" + "\n---"; + """ + # hello + + hello markdown world! + --- + hello: world + --- + """; final String rendered = - "

    hello

    \n

    hello markdown world!

    \n

    hello: world

    \n"; + """ +

    hello

    +

    hello markdown world!

    +

    hello: world

    + """; Map> data = getFrontMatterData(input); @@ -166,8 +213,20 @@ public void yamlInParagraph() { @Test public void yamlOnSecondLine() { - final String input = "hello\n" + "\n---" + "\nhello: world" + "\n---"; - final String rendered = "

    hello

    \n
    \n

    hello: world

    \n"; + final String input = + """ + hello + + --- + hello: world + --- + """; + final String rendered = + """ +

    hello

    +
    +

    hello: world

    + """; Map> data = getFrontMatterData(input); @@ -178,8 +237,16 @@ public void yamlOnSecondLine() { @Test public void nonMatchedStartTag() { - final String input = "----\n" + "test"; - final String rendered = "
    \n

    test

    \n"; + final String input = + """ + ---- + test + """; + final String rendered = + """ +
    +

    test

    + """; Map> data = getFrontMatterData(input); @@ -190,8 +257,22 @@ public void nonMatchedStartTag() { @Test public void inList() { - final String input = "* ---\n" + " ---\n" + "test"; - final String rendered = "
      \n
    • \n
      \n
      \n
    • \n
    \n

    test

    \n"; + final String input = + """ + * --- + --- + test + """; + final String rendered = + """ +
      +
    • +
      +
      +
    • +
    +

    test

    + """; Map> data = getFrontMatterData(input); @@ -202,7 +283,12 @@ public void inList() { @Test public void visitorIgnoresOtherCustomNodes() { - final String input = "---" + "\nhello: world" + "\n---" + "\n"; + final String input = + """ + --- + hello: world + --- + """; YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); Node document = parser.parse(input); @@ -217,7 +303,12 @@ public void visitorIgnoresOtherCustomNodes() { @Test public void nodesCanBeModified() { - final String input = "---" + "\nhello: world" + "\n---" + "\n"; + final String input = + """ + --- + hello: world + --- + """; Node document = parser.parse(input); YamlFrontMatterNode node = (YamlFrontMatterNode) document.getFirstChild().getFirstChild(); @@ -235,7 +326,12 @@ public void nodesCanBeModified() { @Test public void dotInKeys() { - final String input = "---" + "\nms.author: author" + "\n---" + "\n"; + final String input = + """ + --- + ms.author: author + --- + """; Map> data = getFrontMatterData(input); @@ -248,7 +344,13 @@ public void dotInKeys() { @Test public void singleQuotedLiterals() { final String input = - "---" + "\nstring: 'It''s me'" + "\nlist:" + "\n - 'I''m here'" + "\n---" + "\n"; + """ + --- + string: 'It''s me' + list: + - 'I''m here' + --- + """; Map> data = getFrontMatterData(input); @@ -260,12 +362,13 @@ public void singleQuotedLiterals() { @Test public void doubleQuotedLiteral() { final String input = - "---" - + "\nstring: \"backslash: \\\\ quote: \\\"\"" - + "\nlist:" - + "\n - \"hey\"" - + "\n---" - + "\n"; + """ + --- + string: "backslash: \\\\ quote: \\"" + list: + - "hey" + --- + """; Map> data = getFrontMatterData(input); diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java index 345c02d15..e3d43b430 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/ExtensionsIntegrationTest.java @@ -19,16 +19,27 @@ public class ExtensionsIntegrationTest extends RenderingTestCase { @Test public void testImageAttributes() { assertRendering( - "![text](/url.png){height=5 width=6}", - "

    \"text\"

    \n"); + """ + ![text](/url.png){height=5 width=6} + """, + """ +

    text

    + """); } @Test public void testTaskListItems() { assertRendering( - "- [ ] task to do\n- [x] task done\n", - "
      \n
    • task to do
    • \n" - + "
    • task done
    • \n
    \n"); + """ + - [ ] task to do + - [x] task done + """, + """ +
      +
    • task to do
    • +
    • task done
    • +
    + """); } @Override diff --git a/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java b/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java index e7870f411..a72ed959c 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java @@ -80,10 +80,17 @@ public void changedUpdate(DocumentEvent e) {} tabbedPane.addChangeListener(e -> updateOutput(input.getText())); input.setText( - "# Example\n" - + "Enter text *here* and see how it renders on the right.\n\n" - + "* Try\n* this\n\n" - + "```\nor this\n```"); + """ + # Example + Enter text *here* and see how it renders on the right. + + * Try + * this + + ``` + or this + ``` + """); updateOutput(input.getText()); frame.setLayout(new GridLayout()); diff --git a/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java b/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java index 06007e4bd..bb58ab74d 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java @@ -13,7 +13,12 @@ public class DelimitedTest { @Test public void emphasisDelimiters() { String input = - "* *emphasis* \n" + "* **strong** \n" + "* _important_ \n" + "* __CRITICAL__ \n"; + """ + * *emphasis*\s + * **strong**\s + * _important_\s + * __CRITICAL__\s + """; Parser parser = Parser.builder().build(); Node document = parser.parse(input); diff --git a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java index a07519756..1ccd8a8db 100644 --- a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import org.commonmark.node.FencedCodeBlock; -import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; @@ -16,7 +15,13 @@ public class FencedCodeBlockParserTest extends RenderingTestCase { @Test public void backtickInfo() { - Node document = PARSER.parse("```info ~ test\ncode\n```"); + var s = + """ + ```info ~ test + code + ``` + """; + var document = PARSER.parse(s); FencedCodeBlock codeBlock = (FencedCodeBlock) document.getFirstChild(); assertThat(codeBlock.getInfo()).isEqualTo("info ~ test"); assertThat(codeBlock.getLiteral()).isEqualTo("code\n"); @@ -25,34 +30,78 @@ public void backtickInfo() { @Test public void backtickInfoDoesntAllowBacktick() { assertRendering( - "```info ` test\ncode\n```", - "

    ```info ` test\ncode

    \n
    \n"); + """ + ```info ` test + code + ``` + """, + """ +

    ```info ` test + code

    +
    + """); } @Test public void backtickAndTildeCantBeMixed() { - assertRendering("``~`\ncode\n``~`", "

    ~` code ~`

    \n"); + assertRendering( + """ + ``~` + code + ``~` + """, + """ +

    ~` code ~`

    + """); } @Test public void closingCanHaveSpacesAfter() { - assertRendering("```\ncode\n``` ", "
    code\n
    \n"); + assertRendering( + """ + ``` + code + ``` \s + """, + """ +
    code
    +                
    + """); } @Test public void closingCanNotHaveNonSpaces() { - assertRendering("```\ncode\n``` a", "
    code\n``` a\n
    \n"); + assertRendering( + """ + ``` + code + ``` a + """, + """ +
    code
    +                ``` a
    +                
    + """); } @Test public void issue151() { assertRendering( - "```\nthis code\n\nshould not have BRs or paragraphs in it\nok\n```", - "
    this code\n"
    -                        + "\n"
    -                        + "should not have BRs or paragraphs in it\n"
    -                        + "ok\n"
    -                        + "
    \n"); + """ + ``` + this code + + should not have BRs or paragraphs in it + ok + ``` + """, + """ +
    this code
    +
    +                should not have BRs or paragraphs in it
    +                ok
    +                
    + """); } @Override diff --git a/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java b/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java index e606d467e..047508436 100644 --- a/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java @@ -7,149 +7,241 @@ public class ListTightLooseTest extends CoreRenderingTestCase { @Test public void tight() { assertRendering( - "- foo\n" + "- bar\n" + "+ baz\n", - "
      \n" - + "
    • foo
    • \n" - + "
    • bar
    • \n" - + "
    \n" - + "
      \n" - + "
    • baz
    • \n" - + "
    \n"); + """ + - foo + - bar + + baz + """, + """ +
      +
    • foo
    • +
    • bar
    • +
    +
      +
    • baz
    • +
    + """); } @Test public void loose() { assertRendering( - "- foo\n" + "\n" + "- bar\n" + "\n" + "\n" + "- baz\n", - "
      \n" - + "
    • \n" - + "

      foo

      \n" - + "
    • \n" - + "
    • \n" - + "

      bar

      \n" - + "
    • \n" - + "
    • \n" - + "

      baz

      \n" - + "
    • \n" - + "
    \n"); + """ + - foo + + - bar + + + - baz + """, + """ +
      +
    • +

      foo

      +
    • +
    • +

      bar

      +
    • +
    • +

      baz

      +
    • +
    + """); } @Test public void looseNested() { assertRendering( - "- foo\n" + " - bar\n" + "\n" + "\n" + " baz", - "
      \n" - + "
    • foo\n" - + "
        \n" - + "
      • \n" - + "

        bar

        \n" - + "

        baz

        \n" - + "
      • \n" - + "
      \n" - + "
    • \n" - + "
    \n"); + """ + - foo + - bar + + + baz + """, + """ +
      +
    • foo +
        +
      • +

        bar

        +

        baz

        +
      • +
      +
    • +
    + """); } @Test public void looseNested2() { assertRendering( - "- a\n" + " - b\n" + "\n" + " c\n" + "- d\n", - "
      \n" - + "
    • a\n" - + "
        \n" - + "
      • \n" - + "

        b

        \n" - + "

        c

        \n" - + "
      • \n" - + "
      \n" - + "
    • \n" - + "
    • d
    • \n" - + "
    \n"); + """ + - a + - b + + c + - d + """, + """ +
      +
    • a +
        +
      • +

        b

        +

        c

        +
      • +
      +
    • +
    • d
    • +
    + """); } @Test public void looseOuter() { assertRendering( - "- foo\n" + " - bar\n" + "\n" + "\n" + " baz", - "
      \n" - + "
    • \n" - + "

      foo

      \n" - + "
        \n" - + "
      • bar
      • \n" - + "
      \n" - + "

      baz

      \n" - + "
    • \n" - + "
    \n"); + """ + - foo + - bar + + + baz + """, + """ +
      +
    • +

      foo

      +
        +
      • bar
      • +
      +

      baz

      +
    • +
    + """); } @Test public void looseListItem() { assertRendering( - "- one\n" + "\n" + " two\n", - "
      \n" + "
    • \n" + "

      one

      \n" + "

      two

      \n" + "
    • \n" + "
    \n"); + """ + - one + + two + """, + """ +
      +
    • +

      one

      +

      two

      +
    • +
    + """); } @Test public void tightWithBlankLineAfter() { assertRendering( - "- foo\n" + "- bar\n" + "\n", - "
      \n" + "
    • foo
    • \n" + "
    • bar
    • \n" + "
    \n"); + """ + - foo + - bar + + """, + """ +
      +
    • foo
    • +
    • bar
    • +
    + """); } @Test public void tightListWithCodeBlock() { assertRendering( - "- a\n" + "- ```\n" + " b\n" + "\n" + "\n" + " ```\n" + "- c\n", - "
      \n" - + "
    • a
    • \n" - + "
    • \n" - + "
      b\n"
      -                        + "\n"
      -                        + "\n"
      -                        + "
      \n" - + "
    • \n" - + "
    • c
    • \n" - + "
    \n"); + """ + - a + - ``` + b + + + ``` + - c + """, + """ +
      +
    • a
    • +
    • +
      b
      +
      +
      +                
      +
    • +
    • c
    • +
    + """); } @Test public void tightListWithCodeBlock2() { assertRendering( - "* foo\n" + " ```\n" + " bar\n" + "\n" + " ```\n" + " baz\n", - "
      \n" - + "
    • foo\n" - + "
      bar\n"
      -                        + "\n"
      -                        + "
      \n" - + "baz
    • \n" - + "
    \n"); + """ + * foo + ``` + bar + + ``` + baz + """, + """ +
      +
    • foo +
      bar
      +
      +                
      + baz
    • +
    + """); } @Test public void looseEmptyListItem() { assertRendering( - "* a\n" + "*\n" + "\n" + "* c", - "
      \n" - + "
    • \n" - + "

      a

      \n" - + "
    • \n" - + "
    • \n" - + "
    • \n" - + "

      c

      \n" - + "
    • \n" - + "
    \n"); + """ + * a + * + + * c""", + """ +
      +
    • +

      a

      +
    • +
    • +
    • +

      c

      +
    • +
    + """); } @Test public void looseBlankLineAfterCodeBlock() { assertRendering( - "1. ```\n" + " foo\n" + " ```\n" + "\n" + " bar", - "
      \n" - + "
    1. \n" - + "
      foo\n"
      -                        + "
      \n" - + "

      bar

      \n" - + "
    2. \n" - + "
    \n"); + """ + 1. ``` + foo + ``` + + bar + """, + """ +
      +
    1. +
      foo
      +                
      +

      bar

      +
    2. +
    + """); } } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index c3d535e72..8dcdbebe5 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -56,69 +56,222 @@ public void indentedCodeBlockWithMixedTabsAndSpaces() { @Test public void tightListInBlockQuote() { assertRendering( - "> *\n> * a", "
    \n
      \n
    • \n
    • a
    • \n
    \n
    \n"); + """ + > * + > * a + """, + """ +
    +
      +
    • +
    • a
    • +
    +
    + """); } @Test public void looseListInBlockQuote() { // Second line in block quote is considered blank for purpose of loose list assertRendering( - "> *\n>\n> * a", - "
    \n
      \n
    • \n
    • \n

      a

      \n
    • \n
    \n
    \n"); + """ + > * + > + > * a + """, + """ +
    +
      +
    • +
    • +

      a

      +
    • +
    +
    + """); } @Test public void lineWithOnlySpacesAfterListBullet() { - assertRendering("- \n \n foo\n", "
      \n
    • \n
    \n

    foo

    \n"); + assertRendering( + """ + - \s + \s + foo + """, + """ +
      +
    • +
    +

    foo

    + """); } @Test public void listWithTwoSpacesForFirstBullet() { // We have two spaces after the bullet, but no content. With content, the next line would be // required - assertRendering("* \n foo\n", "
      \n
    • foo
    • \n
    \n"); + assertRendering( + """ + * \s + foo + """, + """ +
      +
    • foo
    • +
    + """); } @Test public void orderedListMarkerOnly() { - assertRendering("2.", "
      \n
    1. \n
    \n"); + assertRendering( + """ + 2. + """, + """ +
      +
    1. +
    + """); } @Test public void columnIsInTabOnPreviousLine() { assertRendering( - "- foo\n\n\tbar\n\n# baz\n", - "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n

    baz

    \n"); + """ + - foo + + \tbar + + # baz + """, + """ +
      +
    • +

      foo

      +

      bar

      +
    • +
    +

    baz

    + """); assertRendering( - "- foo\n\n\tbar\n# baz\n", - "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n

    baz

    \n"); + """ + - foo + + \tbar + # baz + """, + """ +
      +
    • +

      foo

      +

      bar

      +
    • +
    +

    baz

    + """); } @Test public void linkLabelWithBracket() { - assertRendering("[a[b]\n\n[a[b]: /", "

    [a[b]

    \n

    [a[b]: /

    \n"); - assertRendering("[a]b]\n\n[a]b]: /", "

    [a]b]

    \n

    [a]b]: /

    \n"); - assertRendering("[a[b]]\n\n[a[b]]: /", "

    [a[b]]

    \n

    [a[b]]: /

    \n"); + assertRendering( + """ + [a[b] + + [a[b]: / + """, + """ +

    [a[b]

    +

    [a[b]: /

    + """); + assertRendering( + """ + [a]b] + + [a]b]: / + """, + """ +

    [a]b]

    +

    [a]b]: /

    + """); + assertRendering( + """ + [a[b]] + + [a[b]]: / + """, + """ +

    [a[b]]

    +

    [a[b]]: /

    + """); } @Test public void linkLabelLength() { String label1 = "a".repeat(999); assertRendering( - "[foo][" + label1 + "]\n\n[" + label1 + "]: /", "

    foo

    \n"); + """ + [foo][%s] + + [%s]: / + """ + .formatted(label1, label1), + """ +

    foo

    + """); assertRendering( - "[foo][x" + label1 + "]\n\n[x" + label1 + "]: /", - "

    [foo][x" + label1 + "]

    \n

    [x" + label1 + "]: /

    \n"); + """ + [foo][x%s] + + [x%s]: / + """ + .formatted(label1, label1), + """ +

    [foo][x%s]

    +

    [x%s]: /

    + """ + .formatted(label1, label1)); assertRendering( - "[foo][\n" + label1 + "]\n\n[\n" + label1 + "]: /", - "

    [foo][\n" + label1 + "]

    \n

    [\n" + label1 + "]: /

    \n"); + """ + [foo][ + %s] + + [ + %s]: / + """ + .formatted(label1, label1), + """ +

    [foo][ + %s]

    +

    [ + %s]: /

    + """ + .formatted(label1, label1)); String label2 = "a\n".repeat(499); assertRendering( - "[foo][" + label2 + "]\n\n[" + label2 + "]: /", "

    foo

    \n"); + """ + [foo][%s] + + [%s]: / + """ + .formatted(label2, label2), + """ +

    foo

    + """); assertRendering( - "[foo][12" + label2 + "]\n\n[12" + label2 + "]: /", - "

    [foo][12" + label2 + "]

    \n

    [12" + label2 + "]: /

    \n"); + """ + [foo][12%s] + + [12%s]: / + """ + .formatted(label2, label2), + """ +

    [foo][12%s]

    +

    [12%s]: /

    + """ + .formatted(label2, label2)); } @Test @@ -143,9 +296,25 @@ public void linkReferenceBackslash() { // Backslash escapes ']', so not a valid link label assertRendering("[\\]: test", "

    []: test

    \n"); // Backslash is a literal, so valid - assertRendering("[a\\b]\n\n[a\\b]: test", "

    a\\b

    \n"); + assertRendering( + """ + [a\\b] + + [a\\b]: test + """, + """ +

    a\\b

    + """); // Backslash escapes `]` but there's another `]`, valid - assertRendering("[a\\]]\n\n[a\\]]: test", "

    a]

    \n"); + assertRendering( + """ + [a\\]] + + [a\\]]: test + """, + """ +

    a]

    + """); } // commonmark/cmark#177 @@ -170,22 +339,29 @@ public void renderEvenRegexpProducesStackoverflow() { @Test public void deeplyIndentedList() { assertRendering( - "* one\n" + " * two\n" + " * three\n" + " * four", - "
      \n" - + "
    • one\n" - + "
        \n" - + "
      • two\n" - + "
          \n" - + "
        • three\n" - + "
            \n" - + "
          • four
          • \n" - + "
          \n" - + "
        • \n" - + "
        \n" - + "
      • \n" - + "
      \n" - + "
    • \n" - + "
    \n"); + """ + * one + * two + * three + * four + """, + """ +
      +
    • one +
        +
      • two +
          +
        • three +
            +
          • four
          • +
          +
        • +
        +
      • +
      +
    • +
    + """); } @Test @@ -193,7 +369,15 @@ public void trailingTabs() { // The tab is not treated as 4 spaces here and so does not result in a hard line break, but // is just preserved. // This matches what commonmark.js did at the time of writing. - assertRendering("a\t\nb\n", "

    a\t\nb

    \n"); + assertRendering( + """ + a\t + b + """, + """ +

    a\t + b

    + """); } @Test @@ -209,36 +393,67 @@ public void unicodePunctuationEmphasis() { @Test public void htmlBlockInterruptingList() { assertRendering( - "- \n", - "
      \n" - + "
    • \n" - + "
    • \n" - + "
    \n"); + """ + - + """, + """ +
      +
    • +
    • +
    + """); assertRendering( - "- \n", - "
      \n" - + "
    • \n" - + "\n"); + """ + - + """, + """ +
        +
      • + + """); } @Test public void emphasisAfterHardLineBreak() { assertRendering( - "Hello \n" + "**Bar**\n" + "Foo\n", - "

        Hello
        \n" + "Bar\n" + "Foo

        \n"); + """ + Hello \s + **Bar** + Foo + """, + """ +

        Hello
        + Bar + Foo

        + """); assertRendering( - "Hello \n" + "**Bar** \n" + "Foo\n", - "

        Hello
        \n" + "Bar
        \n" + "Foo

        \n"); + """ + Hello \s + **Bar** \s + Foo + """, + """ +

        Hello
        + Bar
        + Foo

        + """); } } diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index d0b5bbfdc..f72723843 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -32,17 +32,56 @@ public void textContentText() { assertCompact(s, "foo bar"); assertStripped(s, "foo bar"); - s = "foo foo\n\nbar\nbar"; - assertCompact(s, "foo foo\nbar\nbar"); - assertSeparate(s, "foo foo\n\nbar\nbar"); + s = + """ + foo foo + + bar + bar"""; + assertCompact( + s, + """ + foo foo + bar + bar"""); + assertSeparate( + s, + """ + foo foo + + bar + bar"""); assertStripped(s, "foo foo bar bar"); } @Test public void textContentHeading() { - assertCompact("# Heading\n\nFoo", "Heading\nFoo"); - assertSeparate("# Heading\n\nFoo", "Heading\n\nFoo"); - assertStripped("# Heading\n\nFoo", "Heading: Foo"); + assertCompact( + """ + # Heading + + Foo + """, + """ + Heading + Foo"""); + assertSeparate( + """ + # Heading + + Foo + """, + """ + Heading + + Foo"""); + assertStripped( + """ + # Heading + + Foo + """, + "Heading: Foo"); } @Test @@ -57,9 +96,29 @@ public void textContentEmphasis() { assertCompact(s, "foo foo bar bar"); assertStripped(s, "foo foo bar bar"); - s = "foo\n***foo***\nbar\n\n***bar***"; - assertCompact(s, "foo\nfoo\nbar\nbar"); - assertSeparate(s, "foo\nfoo\nbar\n\nbar"); + s = + """ + foo + ***foo*** + bar + + ***bar*** + """; + assertCompact( + s, + """ + foo + foo + bar + bar"""); + assertSeparate( + s, + """ + foo + foo + bar + + bar"""); assertStripped(s, "foo foo bar bar"); } @@ -67,9 +126,30 @@ public void textContentEmphasis() { public void textContentQuotes() { String s; - s = "foo\n>foo\nbar\n\nbar"; - assertCompact(s, "foo\n«foo\nbar»\nbar"); - assertSeparate(s, "foo\n\n«foo\nbar»\n\nbar"); + s = + """ + foo + >foo + bar + + bar + """; + assertCompact( + s, + """ + foo + «foo + bar» + bar"""); + assertSeparate( + s, + """ + foo + + «foo + bar» + + bar"""); assertStripped(s, "foo «foo bar» bar"); } @@ -94,52 +174,236 @@ public void textContentImages() { public void textContentLists() { String s; - s = "foo\n* foo\n* bar\n\nbar"; - assertCompact(s, "foo\n* foo\n* bar\nbar"); - assertSeparate(s, "foo\n\n* foo\n* bar\n\nbar"); + s = + """ + foo + * foo + * bar + + bar"""; + assertCompact( + s, + """ + foo + * foo + * bar + bar"""); + assertSeparate( + s, + """ + foo + + * foo + * bar + + bar"""); assertStripped(s, "foo foo bar bar"); - s = "foo\n- foo\n- bar\n\nbar"; - assertCompact(s, "foo\n- foo\n- bar\nbar"); - assertSeparate(s, "foo\n\n- foo\n- bar\n\nbar"); + s = + """ + foo + - foo + - bar + + bar + """; + assertCompact( + s, + """ + foo + - foo + - bar + bar"""); + assertSeparate( + s, + """ + foo + + - foo + - bar + + bar"""); assertStripped(s, "foo foo bar bar"); - s = "foo\n1. foo\n2. bar\n\nbar"; - assertCompact(s, "foo\n1. foo\n2. bar\nbar"); - assertSeparate(s, "foo\n\n1. foo\n2. bar\n\nbar"); + s = + """ + foo + 1. foo + 2. bar + + bar + """; + assertCompact( + s, + """ + foo + 1. foo + 2. bar + bar"""); + assertSeparate( + s, + """ + foo + + 1. foo + 2. bar + + bar"""); assertStripped(s, "foo 1. foo 2. bar bar"); - s = "foo\n0) foo\n1) bar\n\nbar"; - assertCompact(s, "foo\n0) foo\n1) bar\nbar"); - assertSeparate(s, "foo\n0) foo\n\n1) bar\n\nbar"); + s = + """ + foo + 0) foo + 1) bar + + bar + """; + assertCompact( + s, + """ + foo + 0) foo + 1) bar + bar"""); + assertSeparate( + s, + """ + foo + 0) foo + + 1) bar + + bar"""); assertStripped(s, "foo 0) foo 1) bar bar"); - s = "bar\n1. foo\n 1. bar\n2. foo"; - assertCompact(s, "bar\n1. foo\n 1. bar\n2. foo"); - assertSeparate(s, "bar\n\n1. foo\n 1. bar\n2. foo"); + s = + """ + bar + 1. foo + 1. bar + 2. foo + """; + assertCompact( + s, + """ + bar + 1. foo + 1. bar + 2. foo"""); + assertSeparate( + s, + """ + bar + + 1. foo + 1. bar + 2. foo"""); assertStripped(s, "bar 1. foo 1. bar 2. foo"); - s = "bar\n* foo\n - bar\n* foo"; - assertCompact(s, "bar\n* foo\n - bar\n* foo"); - assertSeparate(s, "bar\n\n* foo\n - bar\n* foo"); + s = + """ + bar + * foo + - bar + * foo + """; + assertCompact( + s, + """ + bar + * foo + - bar + * foo"""); + assertSeparate( + s, + """ + bar + + * foo + - bar + * foo"""); assertStripped(s, "bar foo bar foo"); - s = "bar\n* foo\n 1. bar\n 2. bar\n* foo"; - assertCompact(s, "bar\n* foo\n 1. bar\n 2. bar\n* foo"); - assertSeparate(s, "bar\n\n* foo\n 1. bar\n 2. bar\n* foo"); + s = + """ + bar + * foo + 1. bar + 2. bar + * foo + """; + assertCompact( + s, + """ + bar + * foo + 1. bar + 2. bar + * foo"""); + assertSeparate( + s, + """ + bar + + * foo + 1. bar + 2. bar + * foo"""); assertStripped(s, "bar foo 1. bar 2. bar foo"); - s = "bar\n1. foo\n * bar\n * bar\n2. foo"; - assertCompact(s, "bar\n1. foo\n * bar\n * bar\n2. foo"); - assertSeparate(s, "bar\n\n1. foo\n * bar\n * bar\n2. foo"); + s = + """ + bar + 1. foo + * bar + * bar + 2. foo + """; + assertCompact( + s, + """ + bar + 1. foo + * bar + * bar + 2. foo"""); + assertSeparate( + s, + """ + bar + + 1. foo + * bar + * bar + 2. foo"""); assertStripped(s, "bar 1. foo bar bar 2. foo"); // For a loose list (not tight) - s = "foo\n\n* bar\n\n* baz"; + s = + """ + foo + + * bar + + * baz + """; // Compact ignores loose - assertCompact(s, "foo\n* bar\n* baz"); + assertCompact( + s, + """ + foo + * bar + * baz"""); // Separate preserves it - assertSeparate(s, "foo\n\n* bar\n\n* baz"); + assertSeparate( + s, + """ + foo + + * bar + + * baz"""); assertStripped(s, "foo bar baz"); } @@ -151,14 +415,43 @@ public void textContentCode() { @Test public void textContentCodeBlock() { String s; - s = "foo\n```\nfoo\nbar\n```\nbar"; + s = + """ + foo + ``` + foo + bar + ``` + bar + """; assertCompact(s, "foo\nfoo\nbar\nbar"); assertSeparate(s, "foo\n\nfoo\nbar\n\nbar"); assertStripped(s, "foo foo bar bar"); - s = "foo\n\n foo\n bar\nbar"; - assertCompact(s, "foo\nfoo\n bar\nbar"); - assertSeparate(s, "foo\n\nfoo\n bar\n\nbar"); + s = + """ + foo + + foo + bar + bar + """; + assertCompact( + s, + """ + foo + foo + bar + bar"""); + assertSeparate( + s, + """ + foo + + foo + bar + + bar"""); assertStripped(s, "foo foo bar bar"); } @@ -166,32 +459,74 @@ public void textContentCodeBlock() { public void textContentBreaks() { String s; - s = "foo\nbar"; - assertCompact(s, "foo\nbar"); - assertSeparate(s, "foo\nbar"); + s = + """ + foo + bar + """; + assertCompact( + s, + """ + foo + bar"""); + assertSeparate( + s, + """ + foo + bar"""); assertStripped(s, "foo bar"); - s = "foo \nbar"; - assertCompact(s, "foo\nbar"); - assertSeparate(s, "foo\nbar"); + s = + """ + foo \s + bar + """; + assertCompact( + s, + """ + foo + bar"""); + assertSeparate( + s, + """ + foo + bar"""); assertStripped(s, "foo bar"); - s = "foo\n___\nbar"; - assertCompact(s, "foo\n***\nbar"); - assertSeparate(s, "foo\n\n***\n\nbar"); + s = + """ + foo + ___ + bar + """; + assertCompact( + s, + """ + foo + *** + bar"""); + assertSeparate( + s, + """ + foo + + *** + + bar"""); assertStripped(s, "foo bar"); } @Test public void textContentHtml() { String html = - "\n" - + " \n" - + " \n" - + " \n" - + "
        \n" - + " foobar\n" - + "
        "; + """ + + + + +
        + foobar +
        """; assertCompact(html, html); assertSeparate(html, html); @@ -201,10 +536,18 @@ public void textContentHtml() { @Test public void testContentNestedLists() { - var s = "List:\n" + "1. 2) 3. \n" + "end"; + var s = + """ + List: + 1. 2) 3.\s + end"""; assertCompact(s, s); - var s2 = "1. A\n 1) B\n 1. Test"; + var s2 = + """ + 1. A + 1) B + 1. Test"""; assertCompact(s2, s2); } diff --git a/pom.xml b/pom.xml index 359a85143..1556a92ae 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,7 @@ 3.14.0 11 + 17 From 8b73380c58301d5758f23d0673a6b657463376e9 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 7 Aug 2026 14:20:47 +1000 Subject: [PATCH 345/345] Don't run Java 11 on CI (but still supported) --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95bdda997..6326e147a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [11, 17, 21, 25] + java: [17, 21, 25] steps: - name: Checkout sources uses: actions/checkout@v7 @@ -33,7 +33,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v5 with: - java-version: 11 + java-version: 17 distribution: 'zulu' - name: Build with coverage @@ -54,7 +54,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v5 with: - java-version: 11 + java-version: 17 distribution: 'zulu' - name: Android Lint checks

    http://example.com?find=\*