From fbf87b8495d00723e8a62ba0824fd7df7fdf0ce5 Mon Sep 17 00:00:00 2001 From: Dan Wyand Date: Fri, 14 Jul 2023 14:34:44 -0400 Subject: [PATCH 001/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] [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/247] [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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] [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/247] [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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] [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/247] [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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] [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/247] [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/247] 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/247] 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/247] 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/247] 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/247] [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/247] [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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] [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/247] [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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] [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/247] [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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] 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/247] [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/247] [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/247] 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 ```

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